From 403506a0b01b78615b84e34a1bb7d561ccdba01a Mon Sep 17 00:00:00 2001
From: forest
Date: Wed, 13 May 2020 13:56:43 -0500
Subject: [PATCH] capacity limiter and general cleanup
---
capsulflask/console.py | 14 +++-
capsulflask/metrics.py | 6 +-
.../02_up_accounts_vms_etc.sql | 3 +-
.../shell_scripts/capacity-avaliable.sh | 25 ++++++
capsulflask/shell_scripts/destroy.sh | 2 +-
capsulflask/shell_scripts/get.sh | 2 +-
capsulflask/shell_scripts/list-ids.sh | 3 +
capsulflask/templates/create-capsul.html | 79 +++++++++----------
capsulflask/virt_model.py | 31 +++++++-
setup.cfg | 6 ++
10 files changed, 121 insertions(+), 50 deletions(-)
create mode 100644 capsulflask/shell_scripts/capacity-avaliable.sh
create mode 100644 capsulflask/shell_scripts/list-ids.sh
diff --git a/capsulflask/console.py b/capsulflask/console.py
index 4df63c2..0cb0e3e 100644
--- a/capsulflask/console.py
+++ b/capsulflask/console.py
@@ -75,7 +75,7 @@ def detail(id):
vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"])
vm["created"] = vm['created'].strftime("%b %d %Y %H:%M")
- vm["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else ""
+ vm["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else ""
return render_template(
"capsul-detail.html",
@@ -92,6 +92,7 @@ def create():
operating_systems = get_model().operating_systems_dict()
ssh_public_keys = get_model().list_ssh_public_keys_for_account(session["account"])
account_balance = get_account_balance()
+ capacity_avaliable = current_app.config["VIRTUALIZATION_MODEL"].capacity_avaliable(512*1024*1024)
errors = list()
created_os = None
@@ -130,6 +131,13 @@ def create():
if len(posted_keys) == 0:
errors.append("At least one SSH Public Key is required")
+ capacity_avaliable = current_app.config["VIRTUALIZATION_MODEL"].capacity_avaliable(vm_sizes[size]['memory_mb']*1024*1024)
+
+ if not capacity_avaliable:
+ errors.append("""
+ host(s) at capacity. no capsuls can be created at this time. sorry.
+ """)
+
if len(errors) == 0:
id = makeCapsulId()
get_model().create_vm(
@@ -157,9 +165,13 @@ def create():
for error in errors:
flash(error)
+ if not capacity_avaliable:
+ print(f"when capsul capacity is restored, send an email to {session['account']}")
+
return render_template(
"create-capsul.html",
created_os=created_os,
+ capacity_avaliable=capacity_avaliable,
account_balance=format(account_balance, '.2f'),
ssh_public_keys=ssh_public_keys,
ssh_public_key_count=len(ssh_public_keys),
diff --git a/capsulflask/metrics.py b/capsulflask/metrics.py
index 828472d..9b6d99f 100644
--- a/capsulflask/metrics.py
+++ b/capsulflask/metrics.py
@@ -181,6 +181,7 @@ def draw_plot_png_bytes(data, scale, size_x=3, size_y=1):
my_plot.xaxis.set_major_formatter(day_formatter)
max_value = reduce(lambda a, b: a if a > b else b, y, scale)
+
average=(sum(y)/len(y))/scale
average=average*1.25+0.1
@@ -195,11 +196,8 @@ def draw_plot_png_bytes(data, scale, size_x=3, size_y=1):
my_plot.fill_between( x, y, color=highlight_color, alpha=0.3)
my_plot.plot(x, y, 'r-', color=highlight_color)
- my_plot.patch.set_facecolor('red')
- my_plot.patch.set_alpha(0.5)
-
if size_y < 4:
- my_plot.set_yticks([0, scale*0.5, scale])
+ my_plot.set_yticks([0, scale])
my_plot.set_ylim(0, scale)
my_plot.xaxis.label.set_color(highlight_color)
diff --git a/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql b/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql
index 50136bb..2c0f131 100644
--- a/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql
+++ b/capsulflask/schema_migrations/02_up_accounts_vms_etc.sql
@@ -71,7 +71,7 @@ CREATE TABLE stripe_checkout_sessions (
INSERT INTO os_images (id, template_image_file_name, description)
VALUES ('alpine311', 'alpine-cloud-2020-04-18.qcow2', 'Alpine Linux 3.11'),
- ('ubuntu18', 'ubuntu-18.04-minimal-cloudimg-amd64.img', 'Ubuntu 18.04 LTS (Bionic Beaver)'),
+ ('ubuntu18', 'ubuntu-18.04-minimal-cloudimg-amd64.img', 'Ubuntu 18.04 LTS (Bionic)'),
('debian10', 'debian-10-genericcloud-amd64-20191117-80.qcow2', 'Debian 10 (Buster)'),
('centos7', 'CentOS-7-x86_64-GenericCloud.qcow2', 'CentOS 7'),
('centos8', 'CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2', 'CentOS 8'),
@@ -86,6 +86,7 @@ VALUES ('f1-s', 5.33, 512, 1, 500),
('f1-xx', 29.66, 8192, 4, 8000),
('f1-xxx', 57.58, 16384, 8, 16000);
+-- this is test data to be removed later
INSERT INTO accounts (email)
VALUES ('forest.n.johnson@gmail.com');
diff --git a/capsulflask/shell_scripts/capacity-avaliable.sh b/capsulflask/shell_scripts/capacity-avaliable.sh
new file mode 100644
index 0000000..affabb3
--- /dev/null
+++ b/capsulflask/shell_scripts/capacity-avaliable.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+## check avaliable RAM and IPv4s
+
+RAM_BYTES_TO_ALLOCATE="$1"
+
+if echo "$RAM_BYTES_TO_ALLOCATE" | grep -vqE "^[0-9]+$"; then
+ echo "RAM_BYTES_TO_ALLOCATE \"$RAM_BYTES_TO_ALLOCATE\" must be an integer"
+ exit 1
+fi
+
+#100GB
+RAM_LIMIT=100000000000
+
+## compare current allocated ram + RAM_BYTES_TO_ALLOCATE with RAM_LIMIT
+
+IPV4_LIMIT=26
+IPV4_COUNT=$(grep ip-add /var/lib/libvirt/dnsmasq/virbr1.status | wc -l)
+
+if [ $IPV4_COUNT -gt $IPV4_LIMIT ]; then
+ echo "IPv4 address limit reached"
+ exit 1
+fi
+
+ echo "yes"
\ No newline at end of file
diff --git a/capsulflask/shell_scripts/destroy.sh b/capsulflask/shell_scripts/destroy.sh
index 2285fe4..d5b1657 100644
--- a/capsulflask/shell_scripts/destroy.sh
+++ b/capsulflask/shell_scripts/destroy.sh
@@ -10,7 +10,7 @@ three_dots() {
vmname="$1"
-if echo "$vmname" | grep -vqE '^capsul-[a-z0-9]{10}$'; then
+if echo "$vmname" | grep -vqE '^(cvm|capsul)-[a-z0-9]{10}$'; then
echo "vmname $vmname must match "'"^capsul-[a-z0-9]{10}$"'
exit 1
fi
diff --git a/capsulflask/shell_scripts/get.sh b/capsulflask/shell_scripts/get.sh
index 33ee75a..b25d22f 100644
--- a/capsulflask/shell_scripts/get.sh
+++ b/capsulflask/shell_scripts/get.sh
@@ -2,7 +2,7 @@
vmname="$1"
-if echo "$vmname" | grep -vqE '^capsul-[a-z0-9]{10}$'; then
+if echo "$vmname" | grep -vqE '^(cvm|capsul)-[a-z0-9]{10}$'; then
echo "vmname $vmname must match "'"^capsul-[a-z0-9]{10}$"'
exit 1
fi
diff --git a/capsulflask/shell_scripts/list-ids.sh b/capsulflask/shell_scripts/list-ids.sh
new file mode 100644
index 0000000..1f1c3b5
--- /dev/null
+++ b/capsulflask/shell_scripts/list-ids.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+virsh list --all | grep running | grep -v ' Id' | grep -v -- '----' | awk '{print $2}' | sort
\ No newline at end of file
diff --git a/capsulflask/templates/create-capsul.html b/capsulflask/templates/create-capsul.html
index 6cb9682..9e05ae4 100644
--- a/capsulflask/templates/create-capsul.html
+++ b/capsulflask/templates/create-capsul.html
@@ -27,12 +27,13 @@
(Must be funded enough to last at least one month at creation time).
You must add funds to your account before you can create a Capsul.
+ {% elif no_ssh_public_keys %}
+ You don't have any ssh public keys yet.
+ You must upload one before you can create a Capsul.
+ {% elif not capacity_avaliable %}
+ Host(s) at capacity. No capsuls can be created at this time. sorry.
{% else %}
- {% if no_ssh_public_keys %}
- You don't have any ssh public keys yet.
- You must upload one before you can create a Capsul.
- {% else %}
-
+
CAPSUL SIZES
============
type monthly cpus mem ssd net*
@@ -46,44 +47,42 @@
* net is calculated as a per-month average
* all VMs come standard with one public IPv4 addr
-
+
Your account balance: ${{ account_balance }}
-
+
-
+
{% endif %}
{% endif %}
diff --git a/capsulflask/virt_model.py b/capsulflask/virt_model.py
index 03b5b64..c33bdcb 100644
--- a/capsulflask/virt_model.py
+++ b/capsulflask/virt_model.py
@@ -9,7 +9,7 @@ from subprocess import run
from capsulflask.db import get_model
def validate_capsul_id(id):
- if not re.match(r"^capsul-[a-z0-9]{10}$", id):
+ if not re.match(r"^(cvm|capsul)-[a-z0-9]{10}$", id):
raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"")
class VirtualMachine:
@@ -19,6 +19,9 @@ class VirtualMachine:
self.ipv6 = ipv6
class VirtualizationInterface:
+ def capacity_avaliable(self, additional_ram_bytes: int) -> bool:
+ pass
+
def get(self, id: str) -> VirtualMachine:
pass
@@ -32,6 +35,9 @@ class VirtualizationInterface:
pass
class MockVirtualization(VirtualizationInterface):
+ def capacity_avaliable(self, additional_ram_bytes):
+ return True
+
def get(self, id):
validate_capsul_id(id)
return VirtualMachine(id, ipv4="1.1.1.1")
@@ -63,6 +69,27 @@ class ShellScriptVirtualization(VirtualizationInterface):
{completedProcess.stderr}
""")
+ def capacity_avaliable(self, additional_ram_bytes):
+ my_args=[join(current_app.root_path, 'shell_scripts/capacity-avaliable.sh'), additional_ram_bytes]
+ completedProcess = run(my_args, capture_output=True)
+
+ if completedProcess.returncode != 0:
+ print(f"""
+ capacity-avaliable.sh exited {completedProcess.returncode} with
+ stdout:
+ {completedProcess.stdout}
+ stderr:
+ {completedProcess.stderr}
+ """)
+ return False
+
+ lines = completedProcess.stdout.splitlines()
+ if not lines[len(lines)-1] == "yes":
+ print("capacity-avaliable.sh exited 0 but did not return \"yes\" ")
+ return False
+
+ return True
+
def get(self, id):
validate_capsul_id(id)
completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True)
@@ -75,7 +102,7 @@ class ShellScriptVirtualization(VirtualizationInterface):
return VirtualMachine(id, ipv4=lines[0])
def list_ids(self) -> list:
- completedProcess = run([join(current_app.root_path, 'shell_scripts/list_ids.sh')], capture_output=True)
+ completedProcess = run([join(current_app.root_path, 'shell_scripts/list-ids.sh')], capture_output=True)
self.validate_completed_process(completedProcess)
return completedProcess.stdout.splitlines()
diff --git a/setup.cfg b/setup.cfg
index 1196a7a..77450a3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,4 +14,10 @@ packages = find:
include_package_data = true
install_requires =
Flask
+ Flask-Mail
psycopg2
+ nanoid
+ matplotlib
+ stripe
+ python-dotenv
+ requests