mirror of
https://github.com/EvernodeXRPL/sashimono.git
synced 2026-04-29 15:38:00 +00:00
Merge branch 'main' into beta1
This commit is contained in:
17
dependencies/user-cgcreate.sh
vendored
17
dependencies/user-cgcreate.sh
vendored
@@ -35,9 +35,12 @@ if [ "$max_swap_kbytes" != "" ] && [ ! ${#max_swap_kbytes} -eq 0 ] && [ "$max_sw
|
||||
! instance_swap_kbytes=$(expr $instance_mem_kbytes + $max_swap_kbytes / $max_instance_count) && echo "Max swap memory limit calculation error." && exit 1
|
||||
fi
|
||||
|
||||
instance_cpu_us=0
|
||||
instance_cpu_quota=0
|
||||
# In the Sashimono configuration, CPU time is 1000000us Sashimono is given max_cpu_us out of it.
|
||||
# Instance allocation is multiplied by number of cores to determined the number of cores per instance and devided by 10 since cfs_period_us is set to 100000us
|
||||
if [ "$max_cpu_us" != "" ] && [ ! ${#max_cpu_us} -eq 0 ] && [ "$max_cpu_us" -gt 0 ]; then
|
||||
! instance_cpu_us=$(expr $max_cpu_us / $max_instance_count) && echo "Max cpu limit calculation error." && exit 1
|
||||
cores=$(grep -c ^processor /proc/cpuinfo)
|
||||
! instance_cpu_quota=$(expr $(expr $cores \* $max_cpu_us) / $(expr $max_instance_count \* 10)) && echo "Max cpu limit calculation error." && exit 1
|
||||
fi
|
||||
|
||||
prefix="sashi"
|
||||
@@ -53,18 +56,18 @@ done
|
||||
has_err=0
|
||||
for user in "${validusers[@]}"; do
|
||||
# Setup user cgroup.
|
||||
if [ $instance_cpu_us -gt 0 ] &&
|
||||
if [ $instance_cpu_quota -gt 0 ] &&
|
||||
! (cgcreate -g cpu:$user$cgroupsuffix &&
|
||||
echo "1000000" > /sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_period_us &&
|
||||
echo "$instance_cpu_us" > /sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_quota_us); then
|
||||
echo "100000" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_period_us &&
|
||||
echo "$instance_cpu_quota" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_quota_us); then
|
||||
echo "CPU cgroup creation for $user failed."
|
||||
has_err=1
|
||||
fi
|
||||
|
||||
if [ $instance_mem_kbytes -gt 0 ] &&
|
||||
! (cgcreate -g memory:$user$cgroupsuffix &&
|
||||
echo "${instance_mem_kbytes}K" > /sys/fs/cgroup/memory/$user$cgroupsuffix/memory.limit_in_bytes &&
|
||||
echo "${instance_swap_kbytes}K" > /sys/fs/cgroup/memory/$user$cgroupsuffix/memory.memsw.limit_in_bytes); then
|
||||
echo "${instance_mem_kbytes}K" >/sys/fs/cgroup/memory/$user$cgroupsuffix/memory.limit_in_bytes &&
|
||||
echo "${instance_swap_kbytes}K" >/sys/fs/cgroup/memory/$user$cgroupsuffix/memory.memsw.limit_in_bytes); then
|
||||
echo "Memory cgroup creation for $user failed."
|
||||
has_err=1
|
||||
fi
|
||||
|
||||
8
dependencies/user-install.sh
vendored
8
dependencies/user-install.sh
vendored
@@ -209,10 +209,14 @@ WantedBy=default.target" >"$user_dir"/.config/systemd/user/ledger_fs.service
|
||||
|
||||
sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user daemon-reload
|
||||
|
||||
# In the Sashimono configuration, CPU time is 1000000us Sashimono is given max_cpu_us out of it.
|
||||
# Instance allocation is multiplied by number of cores to determined the number of cores per instance and devided by 10 since cfs_period_us is set to 100000us
|
||||
cores=$(grep -c ^processor /proc/cpuinfo)
|
||||
cpu_quota=$(expr $(expr $cores \* $cpu) / 10)
|
||||
echo "Setting up user cgroup resources."
|
||||
! (cgcreate -g cpu:$user$cgroupsuffix &&
|
||||
echo "1000000" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_period_us &&
|
||||
echo "$cpu" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_quota_us) && rollback "CGROUP_CPU_CREAT"
|
||||
echo "100000" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_period_us &&
|
||||
echo "$cpu_quota" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_quota_us) && rollback "CGROUP_CPU_CREAT"
|
||||
! (cgcreate -g memory:$user$cgroupsuffix &&
|
||||
echo "${memory}K" >/sys/fs/cgroup/memory/$user$cgroupsuffix/memory.limit_in_bytes &&
|
||||
echo "${swapmem}K" >/sys/fs/cgroup/memory/$user$cgroupsuffix/memory.memsw.limit_in_bytes) && rollback "CGROUP_MEM_CREAT"
|
||||
|
||||
2
examples/crawler/.gitignore
vendored
Normal file
2
examples/crawler/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
137
examples/crawler/app.js
Normal file
137
examples/crawler/app.js
Normal file
@@ -0,0 +1,137 @@
|
||||
const { ContractInstanceManager } = require('./contract-instance-manager');
|
||||
const evernode = require("evernode-js-client");
|
||||
const process = require('process');
|
||||
const fs = require('fs');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const tenantAddress = args[0] || "rKaXLGujsf8LokeQxG6TsoKVq8XWEMKpH";
|
||||
const tenantSecret = args[1] || "ssorCEAHJFhZihUjCbx55j1mP6RnZ";
|
||||
const intervalSec = (args[2] && parseInt(args[2])) || 60;
|
||||
|
||||
console.log(tenantAddress, intervalSec);
|
||||
|
||||
const registryAddress = "r3cNR2bdao1NyvQ5ZuQvCUgqkoWGmgF34E";
|
||||
const evrIssuerAddress = "rfxLPXCcSmwR99dV97yFozBzrzpvCa2VCf";
|
||||
const foundationAddress = "rppVLpTDks7tjAGw9TRcwqMGzuDvzV72Vh";
|
||||
const foundationSecret = "shdAf9oUv1TLVTTR26Ke7w7Gv44HN";
|
||||
|
||||
const contractOwnerPrivateKey = "edfbbf5e66101cbf443c137b66c6b379bd4dfb8274015f7a0accd5cf3f4c640aa65cb83404120ac759609819591ef839b7d222c84f1f08b3012f490586159d2b50";
|
||||
const contractOwnerPublicKey = "ed5cb83404120ac759609819591ef839b7d222c84f1f08b3012f490586159d2b50";
|
||||
const contractBundle = 'contract-bundle.zip';
|
||||
const logFile = 'crawler.log';
|
||||
const statsFile = 'stats.log';
|
||||
|
||||
let instancesCreated = 0;
|
||||
|
||||
async function fundTenant(tenant) {
|
||||
// Send evers to tenant if needed.
|
||||
const lines = await tenant.xrplAcc.getTrustLines('EVR', evrIssuerAddress);
|
||||
if (lines.length === 0 || parseInt(lines[0].balance) < 1) {
|
||||
await tenant.xrplAcc.setTrustLine('EVR', evrIssuerAddress, "99999999");
|
||||
await new evernode.XrplAccount(foundationAddress, foundationSecret).makePayment(tenantAddress, "100000", 'EVR', evrIssuerAddress);
|
||||
}
|
||||
}
|
||||
|
||||
function appendLog(type, msg) {
|
||||
const str = '\n' + new Date().toUTCString() + '\n' + type + '\n' + JSON.stringify(msg) + '\n';
|
||||
fs.appendFileSync(logFile, str);
|
||||
}
|
||||
|
||||
function updateStats(instancesCreated) {
|
||||
fs.writeFileSync(statsFile, JSON.stringify({
|
||||
instancesCreated: instancesCreated
|
||||
}))
|
||||
}
|
||||
|
||||
async function acquireLease(tenant, host, instanceId, contractId, ownerPubKey) {
|
||||
if ((host.maxInstances - host.activeInstances) > 0) {
|
||||
try {
|
||||
console.log(`Acquiring lease in Host ${host.address} (currently ${host.activeInstances} instances)`);
|
||||
const result = await tenant.acquireLease(host.address, {
|
||||
container_name: instanceId,
|
||||
owner_pubkey: ownerPubKey,
|
||||
contract_id: contractId,
|
||||
image: "hp.latest-ubt.20.04-njs.16",
|
||||
config: {}
|
||||
}, { timeout: 60000 });
|
||||
console.log(`Tenant received instance '${result.instance.name}'`);
|
||||
return result.instance;
|
||||
}
|
||||
catch (err) {
|
||||
console.log("Tenant recieved acquire error: ", err)
|
||||
appendLog('AcquireError ' + host.address, err);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`Host ${host.address} full.`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function spawnOnRandomHost(registry, tenant) {
|
||||
|
||||
console.log("------------------------------------------");
|
||||
console.log(new Date().toUTCString());
|
||||
|
||||
const hosts = await registry.getActiveHosts();
|
||||
|
||||
if (hosts.length > 0) {
|
||||
const randomIndex = Math.floor(Math.random() * hosts.length);
|
||||
const host = hosts[randomIndex];
|
||||
|
||||
const contractId = uuidv4();
|
||||
const instance = await acquireLease(tenant, host, contractId, contractId, contractOwnerPublicKey)
|
||||
|
||||
if (instance) {
|
||||
console.log(`Received instance at ${new Date().toUTCString()}`, instance);
|
||||
const instanceMgr = new ContractInstanceManager(contractOwnerPrivateKey, instance.pubkey, instance.ip, instance.user_port, instance.contractId, contractBundle);
|
||||
|
||||
try {
|
||||
await instanceMgr.deployContract();
|
||||
console.log('Instance deployed at', new Date().toUTCString());
|
||||
updateStats(++instancesCreated);
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
console.log("Contract deployment error.", err);
|
||||
appendLog('DeployError ' + instance.ip, err);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('Spawning skipped.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("No active hosts.");
|
||||
}
|
||||
|
||||
// Reschedule same call.
|
||||
setTimeout(() => {
|
||||
spawnOnRandomHost(registry, tenant);
|
||||
}, intervalSec * 1000);
|
||||
}
|
||||
|
||||
async function crawler() {
|
||||
|
||||
const xrplApi = new evernode.XrplApi('wss://hooks-testnet-v2.xrpl-labs.com');
|
||||
evernode.Defaults.set({
|
||||
registryAddress: registryAddress,
|
||||
xrplApi: xrplApi
|
||||
})
|
||||
await xrplApi.connect();
|
||||
|
||||
const tenant = new evernode.TenantClient(tenantAddress, tenantSecret);
|
||||
await tenant.connect();
|
||||
await tenant.prepareAccount();
|
||||
await fundTenant(tenant);
|
||||
|
||||
const registry = new evernode.RegistryClient();
|
||||
await registry.connect()
|
||||
|
||||
// Recursive scheduled call.
|
||||
spawnOnRandomHost(registry, tenant);
|
||||
}
|
||||
|
||||
crawler();
|
||||
117
examples/crawler/contract-instance-manager.js
Normal file
117
examples/crawler/contract-instance-manager.js
Normal file
@@ -0,0 +1,117 @@
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const bson = require('bson');
|
||||
const HotPocket = require('hotpocket-js-client');
|
||||
|
||||
const uploadTimeout = 30000;
|
||||
|
||||
class ContractInstanceManager {
|
||||
|
||||
#ownerPrivKeyHex;
|
||||
#instancePubKeyHex;
|
||||
#ip;
|
||||
#userPort;
|
||||
#contractId;
|
||||
#contractBundle;
|
||||
|
||||
constructor(ownerPrivKeyHex, instancePubKeyHex, ip, userPort, contractId, contractBundle) {
|
||||
this.#ownerPrivKeyHex = ownerPrivKeyHex;
|
||||
this.#instancePubKeyHex = instancePubKeyHex;
|
||||
this.#ip = ip;
|
||||
this.#userPort = userPort;
|
||||
this.#contractId = contractId;
|
||||
this.#contractBundle = contractBundle;
|
||||
}
|
||||
|
||||
async deployContract() {
|
||||
const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), 'evncrawler'));
|
||||
|
||||
try {
|
||||
const hpc = await this.#getHotPocketConnection();
|
||||
await this.#uploadBundle(hpc, this.#contractBundle);
|
||||
await hpc.close();
|
||||
}
|
||||
catch (e) {
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
await fs.rm(tmpdir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async #getHotPocketConnection() {
|
||||
const server = `wss://${this.#ip}:${this.#userPort}`
|
||||
const keys = await HotPocket.generateKeys(this.#ownerPrivKeyHex);
|
||||
const hpc = await HotPocket.createClient([server], keys, {
|
||||
contractId: this.#contractId,
|
||||
trustedServerKeys: [this.#instancePubKeyHex],
|
||||
protocol: HotPocket.protocols.bson
|
||||
});
|
||||
|
||||
// Establish HotPocket connection.
|
||||
if (!await hpc.connect()) {
|
||||
throw `${server} connection failed.`
|
||||
}
|
||||
return hpc;
|
||||
}
|
||||
|
||||
async #uploadBundle(hpc, bundleZipFile) {
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
const uploadTimer = setTimeout(() => reject("Upload timeout."), uploadTimeout);
|
||||
|
||||
const failure = (e) => {
|
||||
clearTimeout(uploadTimer);
|
||||
reject(e);
|
||||
}
|
||||
const success = () => {
|
||||
console.log("Upload complete");
|
||||
clearTimeout(uploadTimer);
|
||||
resolve();
|
||||
}
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
hpc.on(HotPocket.events.contractOutput, (r) => {
|
||||
|
||||
r.outputs.forEach(output => {
|
||||
let result;
|
||||
try {
|
||||
result = bson.deserialize(output);
|
||||
}
|
||||
catch (e) {
|
||||
failure(e);
|
||||
}
|
||||
if (result.type == "uploadResult") {
|
||||
if (result.status == "ok")
|
||||
success();
|
||||
else
|
||||
failure(`Zip upload failed. reason: ${result.status}`);
|
||||
}
|
||||
else {
|
||||
console.log("Unknown contract output.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const fileContent = await fs.readFile(bundleZipFile);
|
||||
|
||||
console.log("Uploading");
|
||||
const input = await hpc.submitContractInput(bson.serialize({
|
||||
type: "upload",
|
||||
content: fileContent
|
||||
}));
|
||||
|
||||
const submission = await input.submissionStatus;
|
||||
console.log(submission.status);
|
||||
if (submission.status != "accepted")
|
||||
failure("Upload submission failed. reason: " + submission.reason);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ContractInstanceManager
|
||||
}
|
||||
5
examples/crawler/contract/contract.config
Normal file
5
examples/crawler/contract/contract.config
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"bin_path": "/usr/bin/node",
|
||||
"bin_args": "index.js"
|
||||
}
|
||||
64
examples/crawler/contract/echo_contract.js
Normal file
64
examples/crawler/contract/echo_contract.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const HotPocket = require("hotpocket-nodejs-contract");
|
||||
const fs = require('fs');
|
||||
|
||||
const exectsFile = "exects.txt";
|
||||
const maxFileRead = 3 * 1024 * 1024;
|
||||
|
||||
function getFileData() {
|
||||
// Read entire file to memory (intentional).
|
||||
let data = fs.readFileSync("exects.txt").toString();
|
||||
|
||||
// Limit max file data returned.
|
||||
if (data.length > maxFileRead) {
|
||||
data = data.substring(data.length - maxFileRead);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// HP smart contract is defined as a function which takes HP ExecutionContext as an argument.
|
||||
// HP considers execution as complete, when this function completes and all the NPL message callbacks are complete.
|
||||
const echoContract = async (ctx) => {
|
||||
|
||||
// We just save execution timestamp as an example state file change.
|
||||
if (!ctx.readonly) {
|
||||
fs.appendFileSync(exectsFile, "ts:" + ctx.timestamp + "\n");
|
||||
|
||||
const stats = fs.statSync(exectsFile);
|
||||
if (stats.size > 300 * 1024 * 1024) // If more than 300 MB, empty the file.
|
||||
fs.truncateSync(exectsFile);
|
||||
}
|
||||
|
||||
// Collection of per-user promises to wait for. Each promise completes when inputs for that user is processed.
|
||||
const userHandlers = [];
|
||||
|
||||
for (const user of ctx.users.list()) {
|
||||
|
||||
// This user's hex pubkey can be accessed from 'user.pubKey'
|
||||
|
||||
// For each user we add a promise to list of promises.
|
||||
userHandlers.push(new Promise(async (resolve) => {
|
||||
|
||||
// The contract need to ensure that all outputs for a particular user is emitted
|
||||
// in deterministic order. Hence, we are processing all inputs for each user sequentially.
|
||||
for (const input of user.inputs) {
|
||||
|
||||
const buf = await ctx.users.read(input);
|
||||
const msg = buf.toString();
|
||||
|
||||
const output = (msg == "ts") ? getFileData() : ("Echoing: " + msg);
|
||||
await user.send(output);
|
||||
|
||||
}
|
||||
|
||||
// The promise gets completed when all inputs for this user are processed.
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
// Wait until all user promises are complete.
|
||||
await Promise.all(userHandlers);
|
||||
}
|
||||
|
||||
const hpc = new HotPocket.Contract();
|
||||
hpc.init(echoContract);
|
||||
24
examples/crawler/contract/package-lock.json
generated
Normal file
24
examples/crawler/contract/package-lock.json
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "contract",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"hotpocket-nodejs-contract": "0.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/hotpocket-nodejs-contract": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/hotpocket-nodejs-contract/-/hotpocket-nodejs-contract-0.5.3.tgz",
|
||||
"integrity": "sha512-IMyH6noQB1w6dcPTLxdrArMpfeT9d9VJPEQ5bxubDi9Gqeu+s+0E2tKP7wDY85ie7cG/IzC9v0TCbTggp2u12w=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"hotpocket-nodejs-contract": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/hotpocket-nodejs-contract/-/hotpocket-nodejs-contract-0.5.3.tgz",
|
||||
"integrity": "sha512-IMyH6noQB1w6dcPTLxdrArMpfeT9d9VJPEQ5bxubDi9Gqeu+s+0E2tKP7wDY85ie7cG/IzC9v0TCbTggp2u12w=="
|
||||
}
|
||||
}
|
||||
}
|
||||
11
examples/crawler/contract/package.json
Normal file
11
examples/crawler/contract/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "ncc build echo_contract.js --minify -o dist",
|
||||
"cpconf": "cp contract.config dist/",
|
||||
"zip": "mkdir -p ../dist && cd dist && zip -r ../../dist/contract-bundle.zip *",
|
||||
"bundle": "npm run build && npm run cpconf && npm run zip"
|
||||
},
|
||||
"dependencies": {
|
||||
"hotpocket-nodejs-contract": "0.5.3"
|
||||
}
|
||||
}
|
||||
2294
examples/crawler/package-lock.json
generated
Normal file
2294
examples/crawler/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
examples/crawler/package.json
Normal file
13
examples/crawler/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"evernode-js-client": "0.4.40",
|
||||
"hotpocket-js-client": "0.5.3",
|
||||
"bson": "4.6.1",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle-contract": "npm --prefix ./contract run bundle",
|
||||
"build": "ncc build app.js --minify -o dist",
|
||||
"bundle": "npm run build && npm run bundle-contract"
|
||||
}
|
||||
}
|
||||
@@ -48,11 +48,17 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install iptables
|
||||
if ! command -v iptables &>/dev/null; then
|
||||
stage "Installing iptables"
|
||||
apt-get install -y iptables
|
||||
fi
|
||||
|
||||
# Load br_netfilter kernel module on startup (if not loaded already).
|
||||
if [[ -z "$(lsmod | grep br_netfilter)" ]]; then
|
||||
echo "Adding br_netfilter"
|
||||
modprobe br_netfilter
|
||||
echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf
|
||||
echo "br_netfilter" >/etc/modules-load.d/br_netfilter.conf
|
||||
fi
|
||||
|
||||
# -------------------------------
|
||||
@@ -92,10 +98,10 @@ if [ $updated -eq 1 ]; then
|
||||
# Create a backup of original, if remount failed replace updated with backup.
|
||||
cp $originalfstab $backup
|
||||
mv "$tmpfstab" $originalfstab
|
||||
if ! mount -o remount / 2>&1 ; then
|
||||
if ! mount -o remount / 2>&1; then
|
||||
mv $backup $originalfstab
|
||||
echo "Re mounting error." && exit 1
|
||||
fi
|
||||
fi
|
||||
echo "Updated fstab."
|
||||
else
|
||||
echo "fstab already configured."
|
||||
@@ -134,7 +140,7 @@ if [ $res -eq 0 ]; then
|
||||
res=$?
|
||||
updated=1
|
||||
elif [ $res -eq 0 ]; then
|
||||
echo "user_allow_other" >> "$tmpconf"
|
||||
echo "user_allow_other" >>"$tmpconf"
|
||||
res=$?
|
||||
updated=1
|
||||
fi
|
||||
@@ -236,7 +242,7 @@ if [ $res -eq 100 ]; then
|
||||
fi
|
||||
fi
|
||||
elif [ $res -eq 0 ]; then
|
||||
echo "GRUB_CMDLINE_LINUX=\"cgroup_enable=memory swapaccount=1\"" >> "$tmpgrub"
|
||||
echo "GRUB_CMDLINE_LINUX=\"cgroup_enable=memory swapaccount=1\"" >>"$tmpgrub"
|
||||
res=$?
|
||||
updated=1
|
||||
fi
|
||||
@@ -251,7 +257,7 @@ if [ $updated -eq 1 ]; then
|
||||
cp /etc/default/grub $grub_backup
|
||||
mv "$tmpgrub" /etc/default/grub
|
||||
rm -r "$tmp"
|
||||
if ! update-grub >/dev/null 2>&1 ; then
|
||||
if ! update-grub >/dev/null 2>&1; then
|
||||
mv $grub_backup /etc/default/grub
|
||||
echo "Grub update failed."
|
||||
exit 1
|
||||
@@ -268,4 +274,4 @@ else
|
||||
echo "Grub already configured."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
exit 0
|
||||
|
||||
@@ -43,6 +43,46 @@ function set_cpu_info() {
|
||||
[ -z $cpu_mhz ] && cpu_mhz=$(lscpu | grep -i "^CPU MHz:" | sed 's/CPU MHz://g' | sed 's/\.[0-9]*//g' | xargs)
|
||||
}
|
||||
|
||||
function enable_evernode_auto_updater() {
|
||||
# Create the service.
|
||||
echo "[Unit]
|
||||
Description=Service for the Evernode auto-update.
|
||||
After=network.target
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/evernode update -q
|
||||
[Install]
|
||||
WantedBy=multi-user.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.service
|
||||
|
||||
# Create a timer for the service (every two hours).
|
||||
echo "[Unit]
|
||||
Description=Timer for the Evernode auto-update.
|
||||
# Allow manual starts
|
||||
RefuseManualStart=no
|
||||
# Allow manual stops
|
||||
RefuseManualStop=no
|
||||
[Timer]
|
||||
Unit=$EVERNODE_AUTO_UPDATE_SERVICE.service
|
||||
OnCalendar=0/2:00:00
|
||||
# Execute job if it missed a run due to machine being off
|
||||
Persistent=true
|
||||
[Install]
|
||||
WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
|
||||
# Reload the systemd daemon.
|
||||
systemctl daemon-reload
|
||||
|
||||
echo "Enabling Evernode auto update service..."
|
||||
systemctl enable $EVERNODE_AUTO_UPDATE_SERVICE.service
|
||||
|
||||
echo "Enabling Evernode auto update timer..."
|
||||
systemctl enable $EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
echo "Starting Evernode auto update timer..."
|
||||
systemctl start $EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
}
|
||||
|
||||
# Check cgroup rule config exists.
|
||||
[ ! -f /etc/cgred.conf ] && echo "cgroups is not configured. Make sure you've installed and configured cgroup-tools." && exit 1
|
||||
|
||||
@@ -269,4 +309,8 @@ if [ ! -f /run/reboot-required.pkgs ] || [ ! -n "$(grep sashimono /run/reboot-re
|
||||
fi
|
||||
|
||||
echo "Sashimono installed successfully."
|
||||
|
||||
# Enable the Evernode Auto Updater Service.
|
||||
[ "$UPGRADE" == "0" ] && enable_evernode_auto_updater
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -28,6 +28,24 @@ function cgrulesengd_servicename() {
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_evernode_auto_updater() {
|
||||
|
||||
echo "Removing Evernode auto update timer..."
|
||||
systemctl stop $EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
systemctl disable $EVERNODE_AUTO_UPDATE_SERVICE.timer
|
||||
service_path="/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer"
|
||||
rm $service_path
|
||||
|
||||
echo "Removing Evernode auto update service..."
|
||||
systemctl stop $EVERNODE_AUTO_UPDATE_SERVICE.service
|
||||
systemctl disable $EVERNODE_AUTO_UPDATE_SERVICE.service
|
||||
service_path="/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.service"
|
||||
rm $service_path
|
||||
|
||||
# Reload the systemd daemon.
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
[ ! -d $SASHIMONO_BIN ] && echo "$SASHIMONO_BIN does not exist. Aborting uninstall." && exit 1
|
||||
|
||||
# Message board---------------------
|
||||
@@ -87,8 +105,10 @@ rm $service_path
|
||||
# Reload the systemd daemon after removing the service
|
||||
systemctl daemon-reload
|
||||
|
||||
echo "Removing Sashimono private docker registry..."
|
||||
$SASHIMONO_BIN/docker-registry-uninstall.sh
|
||||
if [ -f $SASHIMONO_BIN/docker-registry-uninstall.sh ]; then
|
||||
echo "Removing Sashimono private docker registry..."
|
||||
$SASHIMONO_BIN/docker-registry-uninstall.sh
|
||||
fi
|
||||
|
||||
# Delete binaries except message board and sashimnono uninstall script.
|
||||
# We keep uninstall script so user can uninstall again if error occured at later steps.
|
||||
@@ -154,4 +174,7 @@ groupdel $SASHIADMIN_GROUP
|
||||
|
||||
[ "$UPGRADE" == "0" ] && echo "Sashimono uninstalled successfully." || echo "Sashimono uninstalled successfully. Your data has been preserved."
|
||||
|
||||
# Remove the Evernode Auto Updater Service.
|
||||
[ "$UPGRADE" == "0" ] && remove_evernode_auto_updater
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -12,10 +12,13 @@ instances_per_core=3
|
||||
evernode_alias=/usr/bin/evernode
|
||||
log_dir=/tmp/evernode-beta
|
||||
cloud_storage="https://stevernode.blob.core.windows.net/evernode-beta"
|
||||
script_url="$cloud_storage/setup.sh"
|
||||
setup_script_url="$cloud_storage/setup.sh"
|
||||
installer_url="$cloud_storage/installer.tar.gz"
|
||||
licence_url="$cloud_storage/licence.txt"
|
||||
version_timestamp_file="version.timestamp"
|
||||
installer_version_timestamp_file="installer.version.timestamp"
|
||||
setup_version_timestamp_file="setup.version.timestamp"
|
||||
|
||||
|
||||
|
||||
# export vars used by Sashimono installer.
|
||||
export USER_BIN=/usr/bin
|
||||
@@ -33,6 +36,7 @@ export SASHIUSER_PREFIX="sashi"
|
||||
export MB_XRPL_USER="sashimbxrpl"
|
||||
export CG_SUFFIX="-cg"
|
||||
export EVERNODE_REGISTRY_ADDRESS="r3cNR2bdao1NyvQ5ZuQvCUgqkoWGmgF34E"
|
||||
export EVERNODE_AUTO_UPDATE_SERVICE="evernode-auto-update"
|
||||
|
||||
# Private docker registry (not used for now)
|
||||
export DOCKER_REGISTRY_USER="sashidockerreg"
|
||||
@@ -96,7 +100,7 @@ function confirm() {
|
||||
echo -en $1" [Y/n] "
|
||||
local yn=""
|
||||
read yn </dev/tty
|
||||
|
||||
|
||||
# Default choice is 'y'
|
||||
[ -z $yn ] && yn="y"
|
||||
while ! [[ $yn =~ ^[Yy|Nn]$ ]]; do
|
||||
@@ -346,16 +350,18 @@ function uninstall_failure() {
|
||||
}
|
||||
|
||||
function online_version_timestamp() {
|
||||
# Send HTTP HEAD request and get last modified timestamp of the installer package.
|
||||
curl --silent --head $installer_url | grep 'Last-Modified:' | sed 's/[^ ]* //'
|
||||
# Send HTTP HEAD request and get last modified timestamp of the installer package or setup.sh.
|
||||
curl --silent --head $1 | grep 'Last-Modified:' | sed 's/[^ ]* //'
|
||||
}
|
||||
|
||||
function install_evernode() {
|
||||
local upgrade=$1
|
||||
|
||||
# Get installer version (timestamp). We use this later to check for Evernode software updates.
|
||||
local version_timestamp=$(online_version_timestamp)
|
||||
[ -z "$version_timestamp" ] && echo "Online installer not found." && exit 1
|
||||
local installer_version_timestamp=$(online_version_timestamp $installer_url)
|
||||
[ -z "$installer_version_timestamp" ] && echo "Online installer not found." && exit 1
|
||||
# Get setup version (timestamp).
|
||||
local setup_version_timestamp=$(online_version_timestamp $setup_script_url)
|
||||
|
||||
local tmp=$(mktemp -d)
|
||||
cd $tmp
|
||||
@@ -375,7 +381,7 @@ function install_evernode() {
|
||||
|
||||
# Create evernode cli alias at the begining.
|
||||
# So, if the installation attempt failed user can uninstall the failed installation using evernode commands.
|
||||
create_evernode_alias
|
||||
! create_evernode_alias && install_failure
|
||||
|
||||
# Adding ip address as the host description.
|
||||
description=$inetaddr
|
||||
@@ -393,7 +399,8 @@ function install_evernode() {
|
||||
rm -r $tmp
|
||||
|
||||
# Write the verison timestamp to a file for later updated version comparison.
|
||||
echo $version_timestamp > $SASHIMONO_DATA/$version_timestamp_file
|
||||
echo $installer_version_timestamp > $SASHIMONO_DATA/$installer_version_timestamp_file
|
||||
echo $setup_version_timestamp > $SASHIMONO_DATA/$setup_version_timestamp_file
|
||||
}
|
||||
|
||||
function uninstall_evernode() {
|
||||
@@ -425,18 +432,30 @@ function uninstall_evernode() {
|
||||
|
||||
function update_evernode() {
|
||||
echo "Checking for updates..."
|
||||
local latest=$(online_version_timestamp)
|
||||
[ -z "$latest" ] && echo "Could not check for updates. Online installer not found." && exit 1
|
||||
local latest_installer_script_version=$(online_version_timestamp $installer_url)
|
||||
local latest_setup_script_version=$(online_version_timestamp $setup_script_url)
|
||||
[ -z "$latest_installer_script_version" ] && echo "Could not check for updates. Online installer not found." && exit 1
|
||||
|
||||
local current=$(cat $SASHIMONO_DATA/$version_timestamp_file)
|
||||
[ "$latest" == "$current" ] && echo "Your $evernode installation is up to date." && exit 0
|
||||
local current_installer_script_version=$(cat $SASHIMONO_DATA/$installer_version_timestamp_file)
|
||||
local current_setup_script_version=$(cat $SASHIMONO_DATA/$setup_version_timestamp_file)
|
||||
[ "$latest_installer_script_version" == "$current_installer_script_version" ] && [ "$latest_setup_script_version" == "$current_setup_script_version" ] && echo "Your $evernode installation is up to date." && exit 0
|
||||
|
||||
echo "New $evernode update available. Setup will re-install $evernode with updated software. Your account and contract instances will be preserved."
|
||||
$interactive && ! confirm "\nDo you want to install the update?" && exit 1
|
||||
|
||||
uninstall_evernode 1
|
||||
echo "Starting upgrade..."
|
||||
install_evernode 1
|
||||
# Alias for setup.sh is created during 'install_evernode' too.
|
||||
# If only the setup.sh is updated but not the installer, then the alias should be created again.
|
||||
if [ "$latest_installer_script_version" != "$current_installer_script_version" ] ; then
|
||||
uninstall_evernode 1
|
||||
install_evernode 1
|
||||
elif [ "$latest_setup_script_version" != "$current_setup_script_version" ] ; then
|
||||
[ -d $log_dir ] || mkdir -p $log_dir
|
||||
logfile="$log_dir/installer-$(date +%s).log"
|
||||
! create_evernode_alias && echo "Alias creation failed."
|
||||
echo $latest_setup_script_version > $SASHIMONO_DATA/$setup_version_timestamp_file
|
||||
fi
|
||||
|
||||
echo "Upgrade complete."
|
||||
}
|
||||
|
||||
@@ -464,8 +483,9 @@ function create_log() {
|
||||
|
||||
# Create a copy of this same script as a command.
|
||||
function create_evernode_alias() {
|
||||
! curl -fsSL $script_url --output $evernode_alias >> $logfile 2>&1 && install_failure
|
||||
! chmod +x $evernode_alias >> $logfile 2>&1 && install_failure
|
||||
! curl -fsSL $setup_script_url --output $evernode_alias >> $logfile 2>&1 && echo "Error in creating alias." && return 1
|
||||
! chmod +x $evernode_alias >> $logfile 2>&1 && echo "Error in changing permission for the alias." && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function remove_evernode_alias() {
|
||||
@@ -498,6 +518,7 @@ function reg_info() {
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Begin setup execution flow --------------------
|
||||
|
||||
echo "Thank you for trying out $evernode!"
|
||||
|
||||
@@ -20,7 +20,7 @@ appenv = {
|
||||
ACQUIRE_LEASE_TIMEOUT_THRESHOLD: 0.8,
|
||||
ACQUIRE_LEASE_WAIT_TIMEOUT_THRESHOLD: 0.4,
|
||||
SASHI_CLI_PATH: appenv.IS_DEV_MODE ? "../build/sashi" : "/usr/bin/sashi",
|
||||
MB_VERSION: '0.5.2',
|
||||
MB_VERSION: '0.5.3',
|
||||
TOS_HASH: '757A0237B44D8B2BBB04AE2BAD5813858E0AECD2F0B217075E27E0630BA74314' // This is the sha256 hash of TOS text.
|
||||
}
|
||||
Object.freeze(appenv);
|
||||
|
||||
@@ -107,14 +107,15 @@ class MessageBoard {
|
||||
console.log(`Moments exceeded (current ledger:${e.ledger_index}, expiry ledger:${x.expiryLedger}). Destroying ${x.containerName}`);
|
||||
// Expire the current lease agreement (Burn the instance NFT) and re-minting and creating sell offer for the same lease index.
|
||||
const nft = (await (new evernode.XrplAccount(x.tenant)).getNfts())?.find(n => n.NFTokenID == x.containerName);
|
||||
// If there's no nft for this record it should be already burned and instance is destroyed, So we only delete the record.
|
||||
if (!nft)
|
||||
throw `Cannot find a NFT for ${x.containerName}`;
|
||||
console.log(`Cannot find a NFT for ${x.containerName}`);
|
||||
else {
|
||||
const uriInfo = evernode.UtilHelpers.decodeLeaseNftUri(nft.URI);
|
||||
await this.destroyInstance(x.containerName, x.tenant, uriInfo.leaseIndex, true);
|
||||
}
|
||||
|
||||
const uriInfo = evernode.UtilHelpers.decodeLeaseNftUri(nft.URI);
|
||||
await this.destroyInstance(x.containerName, x.tenant, uriInfo.leaseIndex, true);
|
||||
this.activeInstanceCount--;
|
||||
await this.hostClient.updateRegInfo(this.activeInstanceCount);
|
||||
|
||||
/**
|
||||
* Soft deletion for debugging purpose.
|
||||
*/
|
||||
@@ -122,6 +123,8 @@ class MessageBoard {
|
||||
|
||||
// Delete the lease record related to this instance (Permanent Delete).
|
||||
await this.deleteLeaseRecord(x.txHash);
|
||||
|
||||
await this.hostClient.updateRegInfo(this.activeInstanceCount);
|
||||
console.log(`Destroyed ${x.containerName}`);
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@@ -42,7 +42,16 @@ namespace hp
|
||||
constexpr const char *CHOWN_DIR = "chown -R %s:%s %s";
|
||||
|
||||
// Error codes used in create and initiate instance.
|
||||
constexpr const char *INTERNAL_ERROR = "internal_error";
|
||||
constexpr const char *DB_READ_ERROR = "db_read_error";
|
||||
constexpr const char *DB_WRITE_ERROR = "db_write_error";
|
||||
constexpr const char *USER_INSTALL_ERROR = "user_install_error";
|
||||
constexpr const char *INSTANCE_ERROR = "instance_error";
|
||||
constexpr const char *CONF_READ_ERROR = "conf_read_error";
|
||||
constexpr const char *CONTAINER_CONF_ERROR = "container_conf_error";
|
||||
constexpr const char *CONTAINER_START_ERROR = "container_start_error";
|
||||
constexpr const char *CONTAINER_UPDATE_ERROR = "container_update_error";
|
||||
constexpr const char *NO_CONTAINER = "no_container";
|
||||
constexpr const char *DUP_CONTAINER = "dup_container";
|
||||
constexpr const char *MAX_ALLOCATION_REACHED = "max_alloc_reached";
|
||||
constexpr const char *CONTRACT_ID_INVALID = "contractid_bad_format";
|
||||
constexpr const char *DOCKER_IMAGE_INVALID = "docker_image_invalid";
|
||||
@@ -115,7 +124,7 @@ namespace hp
|
||||
const int allocated_count = sqlite::get_allocated_instance_count(db);
|
||||
if (allocated_count == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = DB_READ_ERROR;
|
||||
LOG_ERROR << "Error getting allocated instance count from db.";
|
||||
return -1;
|
||||
}
|
||||
@@ -166,7 +175,7 @@ namespace hp
|
||||
std::string username;
|
||||
if (install_user(user_id, username, instance_resources.cpu_us, instance_resources.mem_kbytes, instance_resources.swap_kbytes, instance_resources.storage_kbytes, container_name, instance_ports, image_name) == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = USER_INSTALL_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -175,7 +184,7 @@ namespace hp
|
||||
if (create_contract(username, owner_pubkey, contract_id, contract_dir, instance_ports, info) == -1 ||
|
||||
create_container(username, image_name, container_name, contract_dir, instance_ports, info) == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = INSTANCE_ERROR;
|
||||
LOG_ERROR << "Error creating hp instance for " << owner_pubkey;
|
||||
// Remove user if instance creation failed.
|
||||
uninstall_user(username, instance_ports, container_name);
|
||||
@@ -184,7 +193,7 @@ namespace hp
|
||||
|
||||
if (sqlite::insert_hp_instance_row(db, info) == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = DB_WRITE_ERROR;
|
||||
LOG_ERROR << "Error inserting instance data into db for " << owner_pubkey;
|
||||
// Remove container and uninstall user if database update failed.
|
||||
docker_remove(username, container_name);
|
||||
@@ -213,13 +222,13 @@ namespace hp
|
||||
const int res = sqlite::is_container_exists(db, container_name, info);
|
||||
if (res == 0)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = NO_CONTAINER;
|
||||
LOG_ERROR << "Given container not found. name: " << container_name;
|
||||
return -1;
|
||||
}
|
||||
else if (info.status != CONTAINER_STATES[STATES::CREATED])
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = DUP_CONTAINER;
|
||||
LOG_ERROR << "Given container is already initiated. name: " << container_name;
|
||||
return -1;
|
||||
}
|
||||
@@ -231,7 +240,7 @@ namespace hp
|
||||
const int config_fd = open(config_file_path.data(), O_RDWR, FILE_PERMS);
|
||||
if (config_fd == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = CONF_READ_ERROR;
|
||||
LOG_ERROR << errno << ": Error opening config file " << config_file_path;
|
||||
return -1;
|
||||
}
|
||||
@@ -246,7 +255,7 @@ namespace hp
|
||||
hpfs::update_service_conf(info.username, hpfs_log_level, is_full_history) == -1 ||
|
||||
hpfs::start_hpfs_systemd(info.username) == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = CONTAINER_CONF_ERROR;
|
||||
LOG_ERROR << "Error when setting up container. name: " << container_name;
|
||||
close(config_fd);
|
||||
return -1;
|
||||
@@ -255,7 +264,7 @@ namespace hp
|
||||
|
||||
if (docker_start(info.username, container_name) == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
error_msg = CONTAINER_START_ERROR;
|
||||
LOG_ERROR << "Error when starting container. name: " << container_name;
|
||||
// Stop started hpfs processes if starting instance failed.
|
||||
hpfs::stop_hpfs_systemd(info.username);
|
||||
@@ -264,8 +273,8 @@ namespace hp
|
||||
|
||||
if (sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::RUNNING]) == -1)
|
||||
{
|
||||
error_msg = INTERNAL_ERROR;
|
||||
LOG_ERROR << "Error when starting container. name: " << container_name;
|
||||
error_msg = CONTAINER_UPDATE_ERROR;
|
||||
LOG_ERROR << "Error when updating container status. name: " << container_name;
|
||||
// Stop started docker and hpfs processes if database update fails.
|
||||
docker_stop(info.username, container_name);
|
||||
hpfs::stop_hpfs_systemd(info.username);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace version
|
||||
{
|
||||
// Sashimono agent version. Written to new configs.
|
||||
constexpr const char *AGENT_VERSION = "0.5.2";
|
||||
constexpr const char *AGENT_VERSION = "0.5.3";
|
||||
|
||||
// Minimum compatible config version (this will be used to validate configs).
|
||||
constexpr const char *MIN_CONFIG_VERSION = "0.5.0";
|
||||
|
||||
Reference in New Issue
Block a user