mirror of
https://github.com/EvernodeXRPL/sashimono.git
synced 2026-07-27 00:50:46 +00:00
Sashimono reconfiguration support in message board and sagent (#205)
This commit is contained in:
@@ -75,7 +75,7 @@ if [ -f /etc/systemd/system/$SASHIMONO_SERVICE.service ] && [ -d $SASHIMONO_BIN
|
||||
&& echo "$evernode is already installed on your host. Use the 'evernode' command to manage your host." \
|
||||
&& exit 1
|
||||
|
||||
[ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] && [ "$1" != "log" ] && [ "$1" != "applyssl" ] && [ "$1" != "transfer" ]\
|
||||
[ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] && [ "$1" != "log" ] && [ "$1" != "applyssl" ] && [ "$1" != "transfer" ] && [ "$1" != "reconfig" ] \
|
||||
&& echomult "$evernode host management tool
|
||||
\nYour host is registered on $evernode.
|
||||
\nSupported commands:
|
||||
@@ -83,6 +83,7 @@ if [ -f /etc/systemd/system/$SASHIMONO_SERVICE.service ] && [ -d $SASHIMONO_BIN
|
||||
\nlist - View contract instances running on this system
|
||||
\nlog - Generate evernode log file.
|
||||
\napplyssl - Apply new SSL certificates for contracts.
|
||||
\reconfig - Change the host configuration.
|
||||
\nupdate - Check and install $evernode software updates
|
||||
\ntransfer - Initiate an $evernode transfer for your machine
|
||||
\nuninstall - Uninstall and deregister from $evernode" \
|
||||
@@ -221,6 +222,16 @@ function validate_inet_addr() {
|
||||
return 0
|
||||
}
|
||||
|
||||
function validate_positive_decimal() {
|
||||
! [[ $1 =~ ^(0*[1-9][0-9]*(\.[0-9]+)?|0+\.[0-9]*[1-9][0-9]*)$ ]] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function validate_ws_url() {
|
||||
! [[ $1 =~ ^(wss?:\/\/)([^\/|^:|^ ]{3,})(:([0-9]{1,5}))?$ ]] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function set_inet_addr() {
|
||||
|
||||
if $interactive ; then
|
||||
@@ -390,17 +401,17 @@ function set_instance_alloc() {
|
||||
|
||||
while true ; do
|
||||
read -p "Specify the total RAM in megabytes to distribute among all contract instances: " ramMB </dev/tty
|
||||
! [[ $ramMB -gt 0 ]] && echo "Invalid amount." || break
|
||||
! [[ $ramMB -gt 0 ]] && echo "Invalid RAM size." || break
|
||||
done
|
||||
|
||||
while true ; do
|
||||
read -p "Specify the total Swap in megabytes to distribute among all contract instances: " swapMB </dev/tty
|
||||
! [[ $swapMB -gt 0 ]] && echo "Invalid amount." || break
|
||||
! [[ $swapMB -gt 0 ]] && echo "Invalid swap size." || break
|
||||
done
|
||||
|
||||
while true ; do
|
||||
read -p "Specify the total disk space in megabytes to distribute among all contract instances: " diskMB </dev/tty
|
||||
! [[ $diskMB -gt 0 ]] && echo "Invalid amount." || break
|
||||
! [[ $diskMB -gt 0 ]] && echo "Invalid disk size." || break
|
||||
done
|
||||
|
||||
alloc_ramKB=$(( ramMB * 1000 ))
|
||||
@@ -428,7 +439,7 @@ function set_lease_amount() {
|
||||
local amount=0
|
||||
while true ; do
|
||||
read -p "Specify the lease amount in EVRs for your contract instances (per moment charge): " amount </dev/tty
|
||||
! [[ $amount =~ ^(0*[1-9][0-9]*(\.[0-9]+)?|0+\.[0-9]*[1-9][0-9]*)$ ]] && echo "Lease amount should be a positive numerical value greater than zero." || break
|
||||
! validate_positive_decimal $amount && echo "Lease amount should be a positive numerical value greater than zero." || break
|
||||
done
|
||||
|
||||
lease_amount=$amount
|
||||
@@ -445,7 +456,7 @@ function set_rippled_server() {
|
||||
|
||||
while true ; do
|
||||
read -p "Specify the rippled URL: " newURL </dev/tty
|
||||
! [[ $newURL =~ ^(wss:\/\/.*)$ ]] && echo "Rippled URL must be a valid URL that starts with 'wss://' ." || break
|
||||
! validate_ws_url $newURL && echo "Rippled URL must be a valid URL that starts with 'wss://' ." || break
|
||||
done
|
||||
|
||||
rippled_server=$newURL
|
||||
@@ -476,6 +487,7 @@ function set_transferee_address() {
|
||||
function set_host_xrpl_secret() {
|
||||
|
||||
if $interactive; then
|
||||
echomult "In order to register in Evernode you need to have an XRPL account with EVRs.\n"
|
||||
local secret=''
|
||||
while true ; do
|
||||
read -p "Specify the XRPL account secret: " secret </dev/tty
|
||||
@@ -712,6 +724,120 @@ function apply_ssl() {
|
||||
done
|
||||
}
|
||||
|
||||
function reconfig() {
|
||||
[ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1
|
||||
|
||||
local mb_user_id=$(id -u "$MB_XRPL_USER")
|
||||
local mb_user_runtime_dir="/run/user/$mb_user_id"
|
||||
|
||||
echomult "\nStaring reconfiguration...\n"
|
||||
|
||||
local saconfig="$SASHIMONO_DATA/sa.cfg"
|
||||
local max_instance_count=$(jq '.system.max_instance_count' $saconfig)
|
||||
local max_cpu_us=$(jq '.system.max_cpu_us' $saconfig)
|
||||
local max_mem_kbytes=$(jq '.system.max_mem_kbytes' $saconfig)
|
||||
local max_swap_kbytes=$(jq '.system.max_swap_kbytes' $saconfig)
|
||||
local max_storage_kbytes=$(jq '.system.max_storage_kbytes' $saconfig)
|
||||
|
||||
local mbconfig="$MB_XRPL_DATA/mb-xrpl.cfg"
|
||||
local cfg_lease_amount=$(jq '.xrpl.leaseAmount' $mbconfig)
|
||||
local cfg_rippled_server=$(jq -r '.xrpl.rippledServer' $mbconfig)
|
||||
|
||||
local update_sashi=0
|
||||
( ( [[ $alloc_instcount -gt 0 ]] && [[ $max_instance_count != $alloc_instcount ]] ) ||
|
||||
( [[ $alloc_cpu -gt 0 ]] && [[ $max_cpu_us != $alloc_cpu ]] ) ||
|
||||
( [[ $alloc_ramKB -gt 0 ]] && [[ $max_mem_kbytes != $alloc_ramKB ]] ) ||
|
||||
( [[ $alloc_swapKB -gt 0 ]] && [[ $max_swap_kbytes != $alloc_swapKB ]] ) ||
|
||||
( [[ $alloc_diskKB -gt 0 ]] && [[ $max_storage_kbytes != $alloc_diskKB ]] ) ) &&
|
||||
update_sashi=1
|
||||
local update_mb=0
|
||||
( ( [ ! -z "$rippled_server" ] && [[ $cfg_rippled_server != $rippled_server ]] ) ||
|
||||
( [[ $lease_amount -gt 0 ]] && [[ $cfg_lease_amount != $lease_amount ]] ) ||
|
||||
( [[ $alloc_instcount -gt 0 ]] && [[ $max_instance_count != $alloc_instcount ]] ) ) &&
|
||||
update_mb=1
|
||||
|
||||
# Update only if changed
|
||||
[ $update_sashi == 0 ] && [ $update_mb == 0 ] && echomult "Given values are already configured!\n" && exit 0
|
||||
|
||||
# Stop the services to stop any activities.
|
||||
# Stop the sashimono service.
|
||||
if [ $update_sashi == 1 ] ; then
|
||||
echomult "Stopping the sashimono...\n"
|
||||
systemctl stop $SASHIMONO_SERVICE
|
||||
fi
|
||||
|
||||
# Stop the message board service.
|
||||
if [ $update_sashi == 1 ] || [ $update_mb == 1 ] ; then
|
||||
echomult "Stopping the message board...\n"
|
||||
sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user stop $MB_XRPL_SERVICE
|
||||
fi
|
||||
|
||||
if [ $update_sashi == 1 ] ; then
|
||||
echomult "Using allocation"
|
||||
[[ $alloc_cpu -gt 0 ]] && echomult "$alloc_cpu Micro Sec CPU" || alloc_cpu=0
|
||||
[[ $alloc_ramKB -gt 0 ]] && echomult "$(GB $alloc_ramKB) RAM" || alloc_ramKB=0
|
||||
[[ $alloc_swapKB -gt 0 ]] && echomult "$(GB $alloc_swapKB) Swap" || alloc_swapKB=0
|
||||
[[ $alloc_diskKB -gt 0 ]] && echomult "$(GB $alloc_diskKB) disk space" || alloc_diskKB=0
|
||||
[[ $alloc_instcount -gt 0 ]] && echomult "Distributed among $alloc_instcount contract instances" || alloc_instcount=0
|
||||
|
||||
echomult "\nConfiguaring sashimono...\n"
|
||||
|
||||
! $SASHIMONO_BIN/sagent reconfig $SASHIMONO_DATA $alloc_instcount $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB &&
|
||||
echomult "\nThere was an error in updating sashimono configuration.\n" && exit 1
|
||||
|
||||
# Update cgroup allocations.
|
||||
( [ $alloc_cpu -gt 0 ] || [ $alloc_ramKB -gt 0 ] || [ $alloc_swapKB -gt 0 ] || [ $alloc_instcount -gt 0 ] ) &&
|
||||
echomult "Updating the cgroup configuration...\n" &&
|
||||
! $SASHIMONO_BIN/user-cgcreate.sh $SASHIMONO_DATA && echomult "\nError occured while upgrading cgroup allocations\n" && exit 1
|
||||
|
||||
# Update disk quotas.
|
||||
if ( [ $alloc_diskKB -gt 0 ] || [ $alloc_instcount -gt 0 ] ) ; then
|
||||
echomult "Updating the disk quotas...\n"
|
||||
|
||||
users=$(cut -d: -f1 /etc/passwd | grep "^$SASHIUSER_PREFIX" | sort)
|
||||
readarray -t userarr <<<"$users"
|
||||
sashiusers=()
|
||||
for user in "${userarr[@]}"; do
|
||||
[ ${#user} -lt 24 ] || [ ${#user} -gt 32 ] || [[ ! "$user" =~ ^$SASHIUSER_PREFIX[0-9]+$ ]] && continue
|
||||
sashiusers+=("$user")
|
||||
done
|
||||
|
||||
max_storage_kbytes=$(jq '.system.max_storage_kbytes' $saconfig)
|
||||
max_instance_count=$(jq '.system.max_instance_count' $saconfig)
|
||||
disk=$(expr $max_storage_kbytes / $max_instance_count)
|
||||
ucount=${#sashiusers[@]}
|
||||
if [ $ucount -gt 0 ]; then
|
||||
for user in "${sashiusers[@]}"; do
|
||||
setquota -g -F vfsv0 "$user" "$disk" "$disk" 0 0 /
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $update_mb == 1 ] ; then
|
||||
[ ! -z "$rippled_server" ] && echomult "Using the rippled address '$rippled_server'."
|
||||
[[ $lease_amount -gt 0 ]] && echomult "Using lease amount $lease_amount EVRs." || lease_amount=0
|
||||
[[ $alloc_instcount -gt 0 ]] || alloc_instcount=0
|
||||
|
||||
echomult "\nConfiguaring message board...\n"
|
||||
|
||||
! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN reconfig $lease_amount $alloc_instcount $rippled_server &&
|
||||
echo "There was an error in updating message board configuration." && exit 1
|
||||
fi
|
||||
|
||||
# Start the sashimono service.
|
||||
if [ $update_sashi == 1 ] ; then
|
||||
echomult "Starting the sashimono...\n"
|
||||
systemctl start $SASHIMONO_SERVICE
|
||||
fi
|
||||
|
||||
# Start the message board service.
|
||||
if [ $update_sashi == 1 ] || [ $update_mb == 1 ] ; then
|
||||
echomult "Starting the message board...\n"
|
||||
sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user start $MB_XRPL_SERVICE
|
||||
fi
|
||||
}
|
||||
|
||||
# Begin setup execution flow --------------------
|
||||
|
||||
echo "Thank you for trying out $evernode!"
|
||||
@@ -839,6 +965,30 @@ elif [ "$mode" == "log" ]; then
|
||||
|
||||
elif [ "$mode" == "applyssl" ]; then
|
||||
apply_ssl $2 $3 $4
|
||||
|
||||
elif [ "$mode" == "reconfig" ]; then
|
||||
alloc_cpu=${2} # CPU microsec to allocate for contract instances (max 1000000).
|
||||
alloc_ramKB=${3} # RAM to allocate for contract instances.
|
||||
alloc_swapKB=${4} # Swap to allocate for contract instances.
|
||||
alloc_diskKB=${5} # Disk space to allocate for contract instances.
|
||||
alloc_instcount=${6} # Total contract instance count.
|
||||
lease_amount=${7} # Contract instance lease amount in EVRs.
|
||||
rippled_server=${8} # Ripple URL
|
||||
|
||||
( [ -z $alloc_cpu ] || [ -z $alloc_ramKB ] || [ -z $alloc_swapKB ] || [ -z $alloc_diskKB ] || [ -z $alloc_instcount ] || [ -z $lease_amount ] ) &&
|
||||
echomult "Invalid arguments.\n Usage: sagent reconfig <cpu microsec> <ram kbytes> <swap kbytes> <disk kbytes> <max instance count> <lease amount> <rippled server>" && exit 1
|
||||
|
||||
[ ! -z $alloc_cpu ] && [ $alloc_cpu != 0 ] && ( ! ( validate_positive_decimal $alloc_cpu && [[ $alloc_cpu -le 1000000 ]] ) ) && echo "Invalid cpu allocation." && exit 1
|
||||
[ ! -z $alloc_ramKB ] && [ $alloc_ramKB != 0 ] && ! validate_positive_decimal $alloc_ramKB && echo "Invalid ram size." && exit 1
|
||||
[ ! -z $alloc_swapKB ] && [ $alloc_swapKB != 0 ] && ! validate_positive_decimal $alloc_swapKB && echo "Invalid swap size." && exit 1
|
||||
[ ! -z $alloc_diskKB ] && [ $alloc_diskKB != 0 ] && ! validate_positive_decimal $alloc_diskKB && echo "Invalid disk size." && exit 1
|
||||
[ ! -z $alloc_instcount ] && [ $alloc_instcount != 0 ] && ! validate_positive_decimal $alloc_instcount && echo "Invalid instance count." && exit 1
|
||||
[ ! -z $lease_amount ] && [ $lease_amount != 0 ] && ! validate_positive_decimal $lease_amount && echo "Invalid lease amount." && exit 1
|
||||
[ ! -z $rippled_server ] && ! validate_ws_url $rippled_server && echo "Rippled URL must be a valid URL that starts with 'wss://' ." && exit 1
|
||||
|
||||
reconfig
|
||||
|
||||
echo "Successfully changed the configuration!"
|
||||
fi
|
||||
|
||||
[ "$mode" != "uninstall" ] && check_installer_pending_finish
|
||||
|
||||
@@ -51,6 +51,9 @@ async function main() {
|
||||
else if (process.argv.length === 3 && process.argv[2] === 'upgrade') {
|
||||
await new Setup().upgrade();
|
||||
}
|
||||
else if ((process.argv.length === 5 || process.argv.length === 6) && process.argv[2] === 'reconfig') {
|
||||
await new Setup().changeConfig(process.argv[3], process.argv[5], process.argv[4]);
|
||||
}
|
||||
else if (process.argv[2] === 'help') {
|
||||
console.log(`Usage:
|
||||
node index.js - Run message board.
|
||||
@@ -62,6 +65,7 @@ async function main() {
|
||||
node index.js deregister - Deregister the host from Evernode.
|
||||
node index.js reginfo - Display Evernode registration info.
|
||||
node index.js upgrade - Upgrade message board data.
|
||||
node index.js reconfig [leaseAmount] [totalInstanceCount] [rippledServer] - Update message board configuration.
|
||||
node index.js help - Print help.`);
|
||||
}
|
||||
else {
|
||||
@@ -76,7 +80,7 @@ async function main() {
|
||||
console.log('Data dir: ' + appenv.DATA_DIR);
|
||||
console.log('Using Sashimono cli: ' + appenv.SASHI_CLI_PATH);
|
||||
|
||||
const mb = new MessageBoard(appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH, appenv.DB_PATH, appenv.SASHI_CLI_PATH, appenv.SASHI_DB_PATH);
|
||||
const mb = new MessageBoard(appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH, appenv.DB_PATH, appenv.SASHI_CLI_PATH, appenv.SASHI_DB_PATH, appenv.SASHI_CONFIG_PATH);
|
||||
await mb.init();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ appenv = {
|
||||
DB_TABLE_NAME: 'leases',
|
||||
DB_UTIL_TABLE_NAME: 'util_data',
|
||||
SASHI_DB_PATH: (appenv.IS_DEV_MODE ? "../build/" : path.join(appenv.DATA_DIR, '../')) + "sa.sqlite",
|
||||
SASHI_CONFIG_PATH: (appenv.IS_DEV_MODE ? "../build/" : path.join(appenv.DATA_DIR, '../')) + "sa.cfg",
|
||||
SASHI_TABLE_NAME: 'instances',
|
||||
LAST_WATCHED_LEDGER: 'last_watched_ledger',
|
||||
ACQUIRE_LEASE_TIMEOUT_THRESHOLD: 0.8,
|
||||
|
||||
@@ -39,6 +39,13 @@ class ConfigHelper {
|
||||
fs.writeFileSync(secretConfigPath, JSON.stringify(secretCfg, null, 2), { mode: 0o600 }); // Set file permission so only current user can read/write.
|
||||
fs.writeFileSync(configPath, JSON.stringify(publicCfg, null, 2), { mode: 0o644 }); // Set file permission so only current user can read/write and others can read.
|
||||
}
|
||||
|
||||
static readSashiConfig(sashiConfigPath) {
|
||||
if (!fs.existsSync(sashiConfigPath))
|
||||
throw `Sashimono configuration file does not exist at ${sashiConfigPath}`;
|
||||
|
||||
return JSON.parse(fs.readFileSync(sashiConfigPath).toString());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -23,7 +23,7 @@ class MessageBoard {
|
||||
#graceTimeoutRef = null;
|
||||
#lastHaltedTime = null;
|
||||
|
||||
constructor(configPath, secretConfigPath, dbPath, sashiCliPath, sashiDbPath) {
|
||||
constructor(configPath, secretConfigPath, dbPath, sashiCliPath, sashiDbPath, sashiConfigPath) {
|
||||
this.configPath = configPath;
|
||||
this.secretConfigPath = secretConfigPath;
|
||||
this.leaseTable = appenv.DB_TABLE_NAME;
|
||||
@@ -37,7 +37,8 @@ class MessageBoard {
|
||||
this.sashiCli = new SashiCLI(sashiCliPath);
|
||||
this.db = new SqliteDatabase(dbPath);
|
||||
this.sashiDb = new SqliteDatabase(sashiDbPath);
|
||||
this.sashiTable = appenv.SASHI_TABLE_NAME
|
||||
this.sashiTable = appenv.SASHI_TABLE_NAME;
|
||||
this.sashiConfigPath = sashiConfigPath;
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -84,10 +85,14 @@ class MessageBoard {
|
||||
// Catch up missed transactions based on the previously updated "last_watched_ledger" record (checkpoint).
|
||||
await this.#catchupMissedLeases().catch(console.error);
|
||||
|
||||
// Load the sashimono config.
|
||||
const sashiConfig = ConfigHelper.readSashiConfig(this.sashiConfigPath);
|
||||
this.activeInstanceCount = this.expiryList.length;
|
||||
console.log(`Active instance count: ${this.activeInstanceCount}`);
|
||||
// Update the registry with the active instance count.
|
||||
await this.hostClient.updateRegInfo(this.activeInstanceCount, this.cfg.version);
|
||||
await this.hostClient.updateRegInfo(this.activeInstanceCount, this.cfg.version, sashiConfig.system.max_instance_count, null, null,
|
||||
sashiConfig.system.max_cpu_us, Math.floor((sashiConfig.system.max_mem_kbytes + sashiConfig.system.max_swap_kbytes) / 1000),
|
||||
Math.floor(sashiConfig.system.max_storage_kbytes / 1000));
|
||||
this.db.close();
|
||||
|
||||
this.xrplApi.on(evernode.XrplApiEvents.LEDGER, async (e) => {
|
||||
@@ -601,7 +606,7 @@ class MessageBoard {
|
||||
// Burn the NFTs and recreate the offer and send back the lease amount back to the tenant.
|
||||
await this.hostClient.expireLease(nfTokenId, tenantAddress).catch(console.error);
|
||||
// We refresh the config here, So if the purchaserTargetPrice is updated by the purchaser service, the new value will be taken.
|
||||
this.hostClient.refreshConfig();
|
||||
await this.hostClient.refreshConfig();
|
||||
const leaseAmount = this.cfg.xrpl.leaseAmount ? this.cfg.xrpl.leaseAmount : parseFloat(this.hostClient.config.purchaserTargetPrice);
|
||||
await this.hostClient.offerLease(leaseIndex, leaseAmount, appenv.TOS_HASH).catch(console.error);
|
||||
}
|
||||
|
||||
@@ -185,6 +185,9 @@ class Setup {
|
||||
const hostClient = new evernode.HostClient(acc.address, acc.secret);
|
||||
await hostClient.connect();
|
||||
|
||||
if (hostClient.config.hostRegFee > (await hostClient.getEVRBalance()))
|
||||
throw `EVR balance in the account is less than the registration fee (${hostClient.config.hostRegFee}EVRs).`;
|
||||
|
||||
// Sometimes we may get 'tecPATH_DRY' error from rippled when some servers in the testnet cluster
|
||||
// haven't still updated the ledger. In such cases, we retry several times before giving up.
|
||||
let attempts = 0;
|
||||
@@ -311,8 +314,8 @@ class Setup {
|
||||
}
|
||||
}
|
||||
|
||||
// Initiate Host Machine Transfer.
|
||||
|
||||
// Initiate Host Machine Transfer.
|
||||
async transfer(transfereeAddress) {
|
||||
console.log("Transferring host...");
|
||||
const acc = this.#getConfig().xrpl;
|
||||
@@ -322,7 +325,162 @@ class Setup {
|
||||
await hostClient.connect();
|
||||
await hostClient.transfer(transfereeAddress);
|
||||
await this.burnMintedNfts(hostClient.xrplAcc);
|
||||
await hostClient.disconnect();
|
||||
}
|
||||
|
||||
// Change the message board configurations.
|
||||
async changeConfig(leaseAmount, rippledServer, totalInstanceCount) {
|
||||
|
||||
// Update the configuration.
|
||||
const cfg = this.#getConfig();
|
||||
|
||||
if (leaseAmount && isNaN(leaseAmount))
|
||||
throw 'Lease amount should be a number';
|
||||
else if (rippledServer && !rippledServer.match(/^(wss?:\/\/)([^\/|^:|^ ]{3,})(:([0-9]{1,5}))?$/g))
|
||||
throw 'Provided Rippled Server is invalid';
|
||||
else if (totalInstanceCount && isNaN(totalInstanceCount))
|
||||
throw 'Maximum instance count should be a number';
|
||||
|
||||
const leaseAmountParsed = leaseAmount ? parseInt(leaseAmount) : 0;
|
||||
const totalInstanceCountParsed = totalInstanceCount ? parseInt(totalInstanceCount) : 0;
|
||||
|
||||
// Return if not changed.
|
||||
if (!totalInstanceCount &&
|
||||
(!leaseAmount || cfg.xrpl.leaseAmount == leaseAmount) &&
|
||||
(!rippledServer || cfg.xrpl.rippledServer == leaseAmount))
|
||||
return;
|
||||
|
||||
await this.recreateLeases(leaseAmountParsed, totalInstanceCountParsed, rippledServer, cfg);
|
||||
|
||||
if (leaseAmountParsed)
|
||||
cfg.xrpl.leaseAmount = leaseAmountParsed;
|
||||
if (rippledServer)
|
||||
cfg.xrpl.rippledServer = rippledServer;
|
||||
this.#saveConfig(cfg);
|
||||
}
|
||||
|
||||
// Recreate unsold NFTs
|
||||
async recreateLeases(leaseAmount, totalInstanceCount, rippledServer, existingCfg) {
|
||||
// Get sold NFTs.
|
||||
const db = new SqliteDatabase(appenv.DB_PATH);
|
||||
const leaseTable = appenv.DB_TABLE_NAME;
|
||||
|
||||
db.open();
|
||||
const leaseRecords = (await db.getValues(leaseTable).finally(() => { db.close() })).filter(i => (i.status === "Acquired" || i.status === "Extended"));
|
||||
const soldCount = leaseRecords.length;
|
||||
|
||||
if (totalInstanceCount && soldCount > totalInstanceCount)
|
||||
throw `There are ${soldCount} active instances, So max instance count cannot be less than that.`;
|
||||
|
||||
const acc = existingCfg.xrpl;
|
||||
let xrplApi;
|
||||
let hostClient;
|
||||
|
||||
async function initClients(rippledServer) {
|
||||
setEvernodeDefaults(acc.registryAddress, rippledServer);
|
||||
xrplApi = new evernode.XrplApi();
|
||||
hostClient = new evernode.HostClient(acc.address, acc.secret, { xrplApi: xrplApi });
|
||||
await xrplApi.connect();
|
||||
await hostClient.connect();
|
||||
}
|
||||
|
||||
async function deinitClients() {
|
||||
await hostClient.disconnect();
|
||||
await xrplApi.disconnect();
|
||||
}
|
||||
|
||||
await initClients(acc.rippledServer);
|
||||
|
||||
// Get unsold NFTs.
|
||||
const unsoldNfts = (await hostClient.xrplAcc.getNfts()).filter(n => n.URI.startsWith(evernode.EvernodeConstants.LEASE_NFT_PREFIX_HEX))
|
||||
.map(n => { return { nfTokenId: n.NFTokenID, leaseIndex: evernode.UtilHelpers.decodeLeaseNftUri(n.URI).leaseIndex }; });
|
||||
const unsoldCount = unsoldNfts.length;
|
||||
|
||||
// Return if not changed.
|
||||
if (!leaseAmount && !rippledServer && (!totalInstanceCount || (soldCount + unsoldCount) == totalInstanceCount)) {
|
||||
await deinitClients();
|
||||
return;
|
||||
}
|
||||
|
||||
async function getVacantLeaseIndexes(includeUnsold = true) {
|
||||
let acquired = includeUnsold ? [] : unsoldNfts.map(n => n.leaseIndex);
|
||||
let vacant = [];
|
||||
for (const l of leaseRecords) {
|
||||
try {
|
||||
const tenantAddress = l.tenant_xrp_address;
|
||||
const nfTokenId = l.container_name;
|
||||
const nft = (await (new evernode.XrplAccount(tenantAddress, null, { xrplApi: xrplApi })).getNfts())?.find(n => n.NFTokenID == nfTokenId);
|
||||
if (nft) {
|
||||
const index = evernode.UtilHelpers.decodeLeaseNftUri(nft.URI).leaseIndex;
|
||||
acquired.push(index);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
let i = 0;
|
||||
while (vacant.length + acquired.length < totalInstanceCount) {
|
||||
if (!acquired.includes(i))
|
||||
vacant.push(i);
|
||||
i++;
|
||||
}
|
||||
return vacant;
|
||||
}
|
||||
|
||||
let nftsToBurn = [];
|
||||
let nftIndexesToCreate = [];
|
||||
// If lease amount is changed we need to burn all the unsold nfts
|
||||
if ((leaseAmount && acc.leaseAmount !== leaseAmount) || (rippledServer && acc.rippledServer !== rippledServer)) {
|
||||
nftsToBurn = unsoldNfts;
|
||||
|
||||
// If total instance count also changed decide the nfts that we need to create.
|
||||
if (totalInstanceCount && (soldCount + unsoldCount) !== totalInstanceCount) {
|
||||
// If less than current count, Create only first chuck of the burned nfts.
|
||||
// If greater than current count, create burned nfts plus extra nfts that are needed.
|
||||
if (totalInstanceCount < soldCount + unsoldCount) {
|
||||
nftIndexesToCreate = nftsToBurn.map(n => n.leaseIndex).sort((a, b) => a - b).slice(0, totalInstanceCount - soldCount);
|
||||
}
|
||||
else {
|
||||
nftIndexesToCreate = await getVacantLeaseIndexes();
|
||||
}
|
||||
}
|
||||
}
|
||||
// If only instance count is changed decide whether we need to add or burn comparing the current count and updated count.
|
||||
else if (totalInstanceCount && (soldCount + unsoldCount) !== totalInstanceCount) {
|
||||
if (totalInstanceCount < soldCount + unsoldCount) {
|
||||
nftsToBurn = unsoldNfts.sort((a, b) => a.leaseIndex - b.leaseIndex).slice(totalInstanceCount - soldCount);
|
||||
nftIndexesToCreate = [];
|
||||
}
|
||||
else {
|
||||
nftsToBurn = [];
|
||||
nftIndexesToCreate = await getVacantLeaseIndexes(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (const nft of nftsToBurn) {
|
||||
try {
|
||||
await hostClient.expireLease(nft.nfTokenId);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// If rippled server is changed, create new nfts from new server.
|
||||
if (rippledServer && rippledServer !== acc.rippledServer) {
|
||||
await initClients(rippledServer);
|
||||
}
|
||||
|
||||
for (const idx of nftIndexesToCreate) {
|
||||
try {
|
||||
await hostClient.offerLease(idx,
|
||||
leaseAmount ? leaseAmount : (acc.leaseAmount ? acc.leaseAmount : parseFloat(this.hostClient.config.purchaserTargetPrice)),
|
||||
appenv.TOS_HASH);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
await deinitClients();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
96
src/main.cpp
96
src/main.cpp
@@ -21,6 +21,7 @@
|
||||
std::cerr << "sagent new [data_dir] [host_addr] [registry_addr] [inst_count] [cpu_us] [ram_kbytes] [swap_kbytes] [disk_kbytes]\n"; \
|
||||
std::cerr << "sagent run [data_dir]\n"; \
|
||||
std::cerr << "sagent upgrade [data_dir]\n"; \
|
||||
std::cerr << "sagent reconfig [data_dir] [inst_count] [cpu_us] [ram_kbytes] [swap_kbytes] [disk_kbytes]\n"; \
|
||||
std::cerr << "Example: sagent run /etc/sashimono\n"; \
|
||||
return -1; \
|
||||
}
|
||||
@@ -40,7 +41,8 @@ int parse_cmd(int argc, char **argv)
|
||||
if ((conf::ctx.command == "new" && argc >= 2 && argc <= 12) ||
|
||||
(conf::ctx.command == "run" && argc >= 2 && argc <= 3) ||
|
||||
(conf::ctx.command == "upgrade" && argc >= 2 && argc <= 3) ||
|
||||
(conf::ctx.command == "version" && argc == 2))
|
||||
(conf::ctx.command == "version" && argc == 2) ||
|
||||
(conf::ctx.command == "reconfig" && argc >= 2 && argc <= 8))
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -208,6 +210,98 @@ int main(int argc, char **argv)
|
||||
if (conf::write_config(conf::cfg) != 0)
|
||||
return -1;
|
||||
}
|
||||
else if (conf::ctx.command == "reconfig")
|
||||
{
|
||||
conf::set_dir_paths(argv[0], (argc >= 3) ? argv[2] : "");
|
||||
|
||||
size_t inst_count = 0, cpu_us = 0, ram_kbytes = 0, swap_kbytes = 0, disk_kbytes = 0;
|
||||
|
||||
if (((argc >= 4) && (util::stoull(argv[3], inst_count) != 0)) ||
|
||||
((argc >= 5) && (util::stoull(argv[4], cpu_us) != 0)) ||
|
||||
((argc >= 6) && (util::stoull(argv[5], ram_kbytes) != 0)) ||
|
||||
((argc >= 7) && (util::stoull(argv[6], swap_kbytes) != 0)) ||
|
||||
((argc >= 8) && (util::stoull(argv[7], disk_kbytes) != 0)))
|
||||
{
|
||||
std::cerr << "Invalid Sashimono Agent config update args.\n";
|
||||
std::cerr << inst_count << ", " << cpu_us << ", " << ram_kbytes << ", "
|
||||
<< swap_kbytes << ", " << disk_kbytes << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (conf::init() != 0)
|
||||
return 1;
|
||||
|
||||
// Return if not changed.
|
||||
if ((inst_count == 0 || conf::cfg.system.max_instance_count == inst_count) &&
|
||||
(cpu_us == 0 || conf::cfg.system.max_cpu_us == cpu_us) &&
|
||||
(ram_kbytes == 0 || conf::cfg.system.max_mem_kbytes == ram_kbytes) &&
|
||||
(swap_kbytes == 0 || conf::cfg.system.max_swap_kbytes == swap_kbytes) &&
|
||||
(disk_kbytes == 0 || conf::cfg.system.max_storage_kbytes == disk_kbytes))
|
||||
return 0;
|
||||
|
||||
salog::init();
|
||||
|
||||
if (hp::init() == -1)
|
||||
return 1;
|
||||
|
||||
std::vector<hp::instance_info> instances;
|
||||
hp::get_instance_list(instances);
|
||||
hp::deinit();
|
||||
|
||||
// If there are active instances, do not allow reducing the resources per instance. Otherwise we allow adjusting resources.
|
||||
if (inst_count != 0 && instances.size() > inst_count)
|
||||
{
|
||||
std::cerr << "There are " << instances.size() << " active instances, So max instance count cannot be less than that.\n";
|
||||
return 1;
|
||||
}
|
||||
else if (instances.size() > 0)
|
||||
{
|
||||
size_t new_count = inst_count != 0 ? inst_count : conf::cfg.system.max_instance_count;
|
||||
size_t new_cpu = cpu_us != 0 ? cpu_us : conf::cfg.system.max_cpu_us;
|
||||
size_t new_ram = ram_kbytes != 0 ? ram_kbytes : conf::cfg.system.max_mem_kbytes;
|
||||
size_t new_swap = swap_kbytes != 0 ? swap_kbytes : conf::cfg.system.max_swap_kbytes;
|
||||
size_t new_disk = disk_kbytes != 0 ? disk_kbytes : conf::cfg.system.max_storage_kbytes;
|
||||
|
||||
if (new_cpu / new_count < conf::cfg.system.max_cpu_us / conf::cfg.system.max_instance_count)
|
||||
{
|
||||
std::cerr << "CPU per instance should be greater than " << conf::cfg.system.max_cpu_us / conf::cfg.system.max_instance_count << " Micro Sec.\n";
|
||||
return 1;
|
||||
}
|
||||
else if (new_ram / new_count < conf::cfg.system.max_mem_kbytes / conf::cfg.system.max_instance_count)
|
||||
{
|
||||
std::cerr << "RAM per instance should be greater than " << conf::cfg.system.max_mem_kbytes / conf::cfg.system.max_instance_count << " KB.\n";
|
||||
return 1;
|
||||
}
|
||||
else if (new_swap / new_count < conf::cfg.system.max_swap_kbytes / conf::cfg.system.max_instance_count)
|
||||
{
|
||||
std::cerr << "Swap per instance should be greater than " << conf::cfg.system.max_swap_kbytes / conf::cfg.system.max_instance_count << " KB.\n";
|
||||
return 1;
|
||||
}
|
||||
else if (new_disk / new_count < conf::cfg.system.max_storage_kbytes / conf::cfg.system.max_instance_count)
|
||||
{
|
||||
std::cerr << "Storage per instance should be greater than " << conf::cfg.system.max_storage_kbytes / conf::cfg.system.max_instance_count << " KB.\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (inst_count > 0)
|
||||
conf::cfg.system.max_instance_count = inst_count;
|
||||
|
||||
if (cpu_us > 0)
|
||||
conf::cfg.system.max_cpu_us = cpu_us;
|
||||
|
||||
if (ram_kbytes > 0)
|
||||
conf::cfg.system.max_mem_kbytes = ram_kbytes;
|
||||
|
||||
if (swap_kbytes > 0)
|
||||
conf::cfg.system.max_swap_kbytes = swap_kbytes;
|
||||
|
||||
if (disk_kbytes > 0)
|
||||
conf::cfg.system.max_storage_kbytes = disk_kbytes;
|
||||
|
||||
if (conf::write_config(conf::cfg) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user