Request mechanism rolled out, migration mechanism incorporated, Code style now inclined toward EAFP, DeleteVM now working, resume/suspend functionality removed

This commit is contained in:
ahmadbilalkhalid 2019-08-12 17:49:28 +05:00
parent 70ec3f832b
commit 945b34d76c
4 changed files with 407 additions and 10840 deletions

View file

@ -12,6 +12,7 @@ python-decouple = "*"
cython = "*"
pylint = "*"
python-etcd3 = {editable = true,git = "https://github.com/kragniz/python-etcd3"}
sshtunnel = "*"
[requires]
python_version = "3.7"

179
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "6f0726e9d014ad330ab36d11b44a15c59316234e343697e46ea2a10ab0997a90"
"sha256": "8f820ace244a315a831e3330707a6ec24895ed73f8fa1646ce6889915aec0db4"
},
"pipfile-spec": 6,
"requires": {
@ -16,6 +16,13 @@
]
},
"default": {
"asn1crypto": {
"hashes": [
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
],
"version": "==0.24.0"
},
"astroid": {
"hashes": [
"sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4",
@ -23,6 +30,81 @@
],
"version": "==2.2.5"
},
"bcrypt": {
"hashes": [
"sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89",
"sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42",
"sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294",
"sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161",
"sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31",
"sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5",
"sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c",
"sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0",
"sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de",
"sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e",
"sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052",
"sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09",
"sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105",
"sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133",
"sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7",
"sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"
],
"version": "==3.1.7"
},
"cffi": {
"hashes": [
"sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774",
"sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d",
"sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90",
"sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b",
"sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63",
"sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45",
"sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25",
"sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3",
"sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b",
"sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647",
"sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016",
"sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4",
"sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb",
"sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753",
"sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7",
"sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9",
"sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f",
"sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8",
"sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f",
"sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc",
"sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42",
"sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3",
"sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909",
"sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45",
"sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d",
"sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512",
"sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff",
"sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"
],
"version": "==1.12.3"
},
"cryptography": {
"hashes": [
"sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c",
"sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643",
"sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216",
"sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799",
"sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a",
"sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9",
"sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc",
"sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8",
"sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53",
"sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1",
"sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609",
"sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292",
"sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e",
"sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6",
"sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed",
"sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d"
],
"version": "==2.7"
},
"cython": {
"hashes": [
"sha256:07efba7b32c082c519b75e3b03821c2f32848e2b3e9986c784bbd8ffaf0666d7",
@ -131,6 +213,13 @@
],
"version": "==0.6.1"
},
"paramiko": {
"hashes": [
"sha256:99f0179bdc176281d21961a003ffdb2ec369daac1a1007241f53374e376576cf",
"sha256:f4b2edfa0d226b70bd4ca31ea7e389325990283da23465d572ed1f70a7583041"
],
"version": "==2.6.0"
},
"protobuf": {
"hashes": [
"sha256:05c36022fef3c7d3562ac22402965c0c2b9fe8421f459bb377323598996e407f",
@ -154,6 +243,12 @@
],
"version": "==3.9.0"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pylint": {
"hashes": [
"sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
@ -162,6 +257,30 @@
"index": "pypi",
"version": "==2.3.1"
},
"pynacl": {
"hashes": [
"sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255",
"sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c",
"sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e",
"sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae",
"sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621",
"sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56",
"sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39",
"sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310",
"sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1",
"sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a",
"sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786",
"sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b",
"sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b",
"sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f",
"sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20",
"sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415",
"sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715",
"sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1",
"sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0"
],
"version": "==1.3.0"
},
"python-decouple": {
"hashes": [
"sha256:1317df14b43efee4337a4aa02914bf004f010cd56d6c4bd894e6474ec8c4fe2d"
@ -181,6 +300,13 @@
],
"version": "==1.12.0"
},
"sshtunnel": {
"hashes": [
"sha256:c813fdcda8e81c3936ffeac47cb69cfb2d1f5e77ad0de656c6dab56aeebd9249"
],
"index": "pypi",
"version": "==0.1.5"
},
"tenacity": {
"hashes": [
"sha256:a0c3c5f7ae0c33f5556c775ca059c12d6fd8ab7121613a713e8b7d649908571b",
@ -225,19 +351,6 @@
"index": "pypi",
"version": "==1.6.2"
},
"ddt": {
"hashes": [
"sha256:474546b4020ce8a2f9550ba8899c28aa2c284c7bbf175bddede98be949d1ca7c",
"sha256:d13e6af8f36238e89d00f4ebccf2bda4f6d1878be560a6600689e42077e164e3"
],
"version": "==1.2.1"
},
"gitdb": {
"hashes": [
"sha256:a3ebbc27be035a2e874ed904df516e35f4a29a778a764385de09de9e0f139658"
],
"version": "==0.6.4"
},
"gitdb2": {
"hashes": [
"sha256:83361131a1836661a155172932a13c08bda2db3674e4caa32368aa6eb02f38c2",
@ -247,10 +360,10 @@
},
"gitpython": {
"hashes": [
"sha256:7428f1cc5e72d53e65c3259d5cebc22fb2b07f973c49d95b3c3d26c64890a3c3",
"sha256:a0f744a4941eac5d99033eb0adcbec83bf443ee173fda4292d92a906aedce952"
"sha256:c15c55ff890cd3a6a8330059e80885410a328f645551b55a91d858bfb3eb2573",
"sha256:df752b6b6f06f11213e91c4925aea7eaf9e37e88fb71c8a7a1aa0a5c10852120"
],
"version": "==2.1.12"
"version": "==2.1.13"
},
"mccabe": {
"hashes": [
@ -296,19 +409,21 @@
},
"pyyaml": {
"hashes": [
"sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3",
"sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043",
"sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7",
"sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265",
"sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391",
"sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778",
"sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225",
"sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955",
"sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e",
"sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190",
"sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd"
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
],
"version": "==5.1.1"
"version": "==5.1.2"
},
"six": {
"hashes": [
@ -317,12 +432,6 @@
],
"version": "==1.12.0"
},
"smmap": {
"hashes": [
"sha256:0e2b62b497bd5f0afebc002eda4d90df9d209c30ef257e8673c90a6b5c119d62"
],
"version": "==0.9.0"
},
"smmap2": {
"hashes": [
"sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde",

10644
log.txt

File diff suppressed because it is too large Load diff

423
main.py
View file

@ -12,21 +12,29 @@ import logging
import subprocess
import threading
import time
import traceback
import sshtunnel
import errno
from etcd3_wrapper import Etcd3Wrapper
from dataclasses import dataclass
from typing import Union
from functools import wraps
from string import Template
from os.path import join
from ucloud_common.rbd import RBD
from ucloud_common.vm import VmPool, VMEntry, VMStatus
from etcd3_wrapper import Etcd3Wrapper
from ucloud_common.vm import VmPool, VMStatus, VMEntry
from ucloud_common.host import HostPool, HostEntry
from ucloud_common.request import RequestEntry, RequestPool, RequestType
from ucloud_common.helpers import get_ipv4_address
running_vms = []
vnc_port_pool = list(range(0, 100))
client = Etcd3Wrapper()
VM_POOL = None
HOST_POOL = None
vm_pool = None
host_pool = None
request_pool = None
logging.basicConfig(
level=logging.DEBUG,
@ -37,16 +45,37 @@ logging.basicConfig(
)
def get_start_command_args(image_path, vnc_port, migration=False, migration_port=4444):
_args = ("-drive file=$image_path,format=raw,if=virtio,cache=none"
" -m 1024 -device virtio-rng-pci -enable-kvm -vnc :$vnc_port")
if migration:
_args = _args + " -incoming tcp:0:$migration_port"
args_template = Template(_args)
if migration:
args = args_template.substitute(image_path=image_path, vnc_port=vnc_port,
migration_port=migration_port)
else:
args = args_template.substitute(image_path=image_path, vnc_port=vnc_port)
return args.split(" ")
@dataclass
class VM:
key: str
vm: qmp.QEMUMachine
handle: qmp.QEMUMachine
def __repr__(self):
return f"VM({self.key})"
def update_heartbeat(host: HostEntry):
while True:
host.update_heartbeat()
HOST_POOL.put(host)
host_pool.put(host)
time.sleep(10)
logging.info(f"Updated last heartbeat time {host.last_heartbeat}")
@ -58,7 +87,7 @@ def need_running_vm(func):
vm = get_vm(running_vms, e.key)
if vm:
try:
status = vm.vm.command("query-status")
status = vm.handle.command("query-status")
logging.debug(f"VM Status Check - {status}")
except OSError:
logging.info(
@ -75,119 +104,79 @@ def need_running_vm(func):
return wrapper
def create_vm(vm):
image = client.get(f"/v1/image/{vm.value['image_uuid']}", value_in_json=True)
if image:
logging.debug(image)
logging.info("Creating New VM...")
_command_to_create = f"rbd clone images/{vm.image_uuid}@protected uservms/{vm.uuid}"
try:
subprocess.call(_command_to_create.split(" "))
vm.status = VMStatus.requested_start
VM_POOL.put(vm)
except:
logging.exception("Can't clone image")
else:
logging.info(f"Image not found for {vm.image_uuid}")
def start_vm(e):
vm_path = f"rbd:uservms/{e.uuid}"
def create_vm(vm_entry: VMEntry):
_command_to_create = f"rbd clone images/{vm_entry.image_uuid}@protected uservms/{vm_entry.uuid}"
try:
user_vms = RBD.ls("uservms")
except:
logging.info("Can't access uservms pool")
return
if e.uuid not in user_vms:
logging.info(f"Image file of vm {e.key} does not exists")
logging.info(f"Deleting vm {e.key}")
client.client.delete(e.key)
return
_vm = get_vm(running_vms, e.key)
if _vm:
logging.info(f"{e.key} already running")
e.status = VMStatus.running
VM_POOL.put(e)
return
# FIXME: There should be better vnc port allocation scheme
vm = qmp.QEMUMachine(
"/usr/bin/qemu-system-x86_64",
test_dir="vm_socklog",
args=[
vm_path,
"-boot",
"c", # First Boot Hard drive
"-m",
"1024", # RAM limit
# Ever growing port number
"-vnc",
f":{vnc_port_pool.pop(0)}", # Allow VNC
],
)
try:
logging.info(f"Starting {e.key}")
vm.launch()
if vm.is_running():
running_vms.append(VM(e.key, vm))
e.status = VMStatus.running
VM_POOL.put(e)
else:
e.declare_killed()
VM_POOL.put(e)
subprocess.check_call(_command_to_create.split(" "))
except subprocess.CalledProcessError as e:
if e.returncode == errno.EEXIST:
logging.debug(f"Image for vm {vm_entry.uuid} exists")
# File Already exists. No Problem Continue
return
except (qmp.QEMUMachineError, TypeError):
logging.exception(f"Machine Error Occurred on {e.key}")
e.declare_killed()
VM_POOL.put(e)
else:
# This exception catches all other exceptions
# i.e FileNotFound (BaseImage), pool Does Not Exists etc.
logging.exception(f"Can't clone image - {e}")
else:
logging.info(f"Started Successfully {e.key}")
logging.info("New VM Created")
def start_vm(vm_entry: VMEntry):
_vm = get_vm(running_vms, vm_entry.key)
# VM already running. No need to proceed further.
if _vm:
logging.info(f"VM {vm_entry.uuid} already running")
return
else:
create_vm(vm_entry)
logging.info(f"Starting {vm_entry.key}")
# FIXME: There should be better vnc port allocation scheme
vm = qmp.QEMUMachine(
"/usr/bin/qemu-system-x86_64",
args=get_start_command_args(vm_entry.path, vnc_port_pool.pop(0)),
)
try:
vm.launch()
except (qmp.QEMUMachineError, TypeError, Exception):
vm_entry.declare_killed()
vm_entry.add_log(f"Machine Error occurred - {traceback.format_exc()}")
vm_pool.put(vm_entry)
else:
running_vms.append(VM(vm_entry.key, vm))
vm_entry.status = VMStatus.running
vm_entry.add_log("Started successfully")
vm_pool.put(vm_entry)
@need_running_vm
def suspend_vm(e):
vm = get_vm(running_vms, e.key)
vm.vm.command("stop")
if vm.vm.command("query-status")["status"] == "paused":
e.status = VMStatus.suspended
VM_POOL.put(e)
logging.info(f"Successfully suspended VM {e.key}")
else:
logging.info(f"Suspending VM {e.key} failed")
@need_running_vm
def resume_vm(e):
vm = get_vm(running_vms, e.key)
vm.vm.command("cont")
if vm.vm.command("query-status")["status"] == "running":
e.status = VMStatus.running
VM_POOL.put(e)
logging.info(f"Successfully resumed VM {e.key}")
else:
logging.info(f"Resuming VM {e.key} failed")
@need_running_vm
def shutdown_vm(e):
vm = get_vm(running_vms, e.key)
vm.vm.shutdown()
if not vm.vm.is_running():
logging.info(f"VM {e.key} shutdown successfully")
e.status = VMStatus.stopped
VM_POOL.put(e)
def stop_vm(vm_entry):
vm = get_vm(running_vms, vm_entry.key)
vm.handle.shutdown()
if not vm.handle.is_running():
vm_entry.add_log("Shutdown successfully")
vm_entry.declare_stopped()
vm_pool.put(vm_entry)
running_vms.remove(vm)
def delete_vm(e):
# TODO: Delete VM Image From CEPH
logging.info(f"Deleting VM {e.key}")
shutdown_vm(e)
client.client.delete(e.key)
def delete_vm(vm_entry):
logging.info(f"Deleting VM {vm_entry}")
stop_vm(vm_entry)
path_without_protocol = vm_entry.path[vm_entry.path.find(":") + 1:]
try:
rc = subprocess.call(f"rbd rm {path_without_protocol}".split(" "))
except FileNotFoundError as e:
logging.exception(e)
except Exception as e:
logging.exception(f"Unknown error occurred - {e}")
else:
if rc == 0:
client.client.delete(vm_entry.key)
else:
logging.info("Some unknown problem occur while deleting vm file")
def get_vm(vm_list: list, vm_key) -> Union[VM, None]:
@ -195,87 +184,199 @@ def get_vm(vm_list: list, vm_key) -> Union[VM, None]:
def maintenance(host):
_vms = VM_POOL.by_host(host.key)
alleged_running_vms = VM_POOL.by_status("RUNNING", _vms)
# To capture vm running according to running_vms list
for vm in alleged_running_vms:
_vm = get_vm(running_vms, vm.key)
if (_vm and not _vm.vm.is_running()) or _vm is None:
logging.debug(f"{_vm} {vm.key}")
logging.info(
f"{vm.key} is not running but is said to be running"
)
logging.info(f"Updating {vm.key} status to KILLED")
vm.declare_killed()
VM_POOL.put(vm)
running_vms.remove(vm)
# This is to capture successful migration of a VM.
# Suppose, this host is running "vm1" and user initiated
# request to migrate this "vm1" to some other host. On,
# successful migration the destination host would set
# the vm hostname to itself. Thus, we are checking
# whether this host vm is successfully migrated. If yes
# then we shutdown "vm1" on this host.
for vm in running_vms:
with vm_pool.get_put(vm.key) as vm_entry:
if vm_entry.hostname != host.key and not vm_entry.in_migration:
vm.handle.shutdown()
vm_entry.add_log("VM on source host shutdown.")
# To check vm running according to etcd entries
alleged_running_vms = vm_pool.by_status("RUNNING", vm_pool.by_host(host.key))
for vm_entry in alleged_running_vms:
_vm = get_vm(running_vms, vm_entry.key)
# Whether, the allegedly running vm is in our
# running_vms list or not if it is said to be
# running on this host but it is not then we
# need to shut it down
# This is to capture poweroff/shutdown of a VM
# initiated by user inside VM. OR crash of VM by some
# user running process
if (_vm and not _vm.handle.is_running())\
or not _vm:
vm_entry.add_log(f"{vm_entry.key} is not running but is said to be running."
"So, shutting it down and declare it killed")
vm_entry.declare_killed()
vm_pool.put(vm_entry)
if _vm:
running_vms.remove(_vm)
def transfer_vm(request_event):
# This function would run on source host i.e host on which the vm
# is running initially. This host would be responsible for transferring
# vm state to destination host.
_host, _port = request_event.parameters["host"], request_event.parameters["port"]
_uuid = request_event.uuid
_destination = request_event.destination_host_key
vm = get_vm(running_vms, join("/v1/vm", _uuid))
if vm:
tunnel = sshtunnel.SSHTunnelForwarder(
(_host, 22),
ssh_username="meow",
ssh_pkey="~/.ssh/id_rsa",
ssh_private_key_password="***REMOVED***",
remote_bind_address=('127.0.0.1', _port),
)
try:
tunnel.start()
except sshtunnel.BaseSSHTunnelForwarderError:
logging.exception(f"Couldn't establish connection to ({_host}, 22)")
else:
vm.handle.command("migrate", uri=f"tcp:{_host}:{tunnel.local_bind_port}")
status = vm.handle.command("query-migrate")["status"]
while status not in ["failed", "completed"]:
time.sleep(2)
status = vm.handle.command("query-migrate")["status"]
with vm_pool.get_put(request_event.uuid) as source_vm:
if status == "failed":
source_vm.add_log("Migration Failed")
elif status == "completed":
# If VM is successfully migrated then shutdown the VM
# on this host and update hostname to destination host key
source_vm.add_log("Successfully migrated")
source_vm.hostname = _destination
running_vms.remove(vm)
vm.handle.shutdown()
source_vm.in_migration = False # VM transfer finished
finally:
tunnel.close()
def init_vm_migration(vm_entry, destination_host_key):
# This function would run on destination host i.e host on which the vm
# would be transferred after migration. This host would be responsible
# for starting VM that would receive state of VM running on source host.
_vm = get_vm(running_vms, vm_entry.key)
if _vm:
# VM already running. No need to proceed further.
logging.log(f"{_vm.key} Already running")
return
logging.info(f"Starting {vm_entry.key}")
# FIXME: There should be better vnc port allocation scheme
actual_vm = qmp.QEMUMachine(
"/usr/bin/qemu-system-x86_64",
args=get_start_command_args(vm_entry.path, 100, migration=True, migration_port=4444),
)
try:
actual_vm.launch()
except Exception as e:
# We don't care whether MachineError or any other error occurred
logging.exception(e)
actual_vm.shutdown()
else:
vm_entry.in_migration = True
vm_pool.put(vm_entry)
running_vms.append(VM(vm_entry.key, actual_vm))
r = RequestEntry.from_scratch(type=RequestType.TransferVM,
hostname=vm_entry.hostname,
parameters={
"host": get_ipv4_address(),
"port": 4444,
},
uuid=vm_entry.uuid,
destination_host_key=destination_host_key
)
request_pool.put(r)
def main():
argparser = argparse.ArgumentParser()
argparser.add_argument(
"hostname", help="Name of this host. e.g /v1/host/1"
)
argparser.add_argument("hostname", help="Name of this host. e.g /v1/host/1")
args = argparser.parse_args()
global HOST_POOL, VM_POOL
HOST_POOL = HostPool(client, "/v1/host")
VM_POOL = VmPool(client, "/v1/vm")
global host_pool, vm_pool, request_pool
host_pool = HostPool(client, "/v1/host")
vm_pool = VmPool(client, "/v1/vm")
request_pool = RequestPool(client, "/v1/request")
host = HOST_POOL.get(args.hostname)
host = host_pool.get(args.hostname)
if not host:
print("No Such Host")
exit(1)
logging.info(f"{'*' * 5} Session Started {'*' * 5}")
# It is seen that under heavy load, timeout event doesn't come
# in a predictive manner which delays heart beat update which
# in turn misunderstood by scheduler that the host is dead
# when it is actually alive. So, to ensure that we update the
# heart beat in a predictive manner we start Heart beat updating
# mechanism in separated thread
heartbeat_updating_thread = threading.Thread(target=update_heartbeat, args=(host,))
heartbeat_updating_thread.start()
try:
heartbeat_updating_thread.start()
except Exception as e:
print("No Need To Go Further. Our heartbeat updating mechanism is not working")
logging.exception(e)
exit(-1)
for events_iterator in [
client.get_prefix("/v1/vm/", value_in_json=True),
client.watch_prefix("/v1/vm/", timeout=10, value_in_json=True),
client.get_prefix("/v1/request/", value_in_json=True),
client.watch_prefix("/v1/request/", timeout=10, value_in_json=True),
]:
for e in events_iterator:
e = VMEntry(e)
for request_event in events_iterator:
request_event = RequestEntry(request_event)
if e.status == "TIMEOUT":
logging.info("Timeout")
if request_event.type == "TIMEOUT":
logging.info("Timeout Event")
maintenance(host)
continue
# TODO: Re-evaluate Migration Design
if hasattr(e, "migration_destination"):
e_migration_destination = e.value["migration_destination"]
else:
e_migration_destination = ""
# If the event is directed toward me OR I am destination of a InitVMMigration
if hasattr(request_event, "hostname") and request_event.hostname == host.key or\
hasattr(request_event, "destination") and request_event.destination == host.key:
request_pool.client.client.delete(request_event.key)
vm_entry = vm_pool.get(request_event.uuid)
# If the event is directed toward me or
# I am destination of a REQUESTED_MIGRATION
if e.hostname == host.key or e_migration_destination == host.key:
logging.debug(f"EVENT: {e}")
logging.debug(f"EVENT: {request_event}")
if e.status == "SCHEDULED_DEPLOY":
create_vm(e)
if request_event.type == RequestType.StartVM:
start_vm(vm_entry)
elif e.status == "REQUESTED_SUSPEND":
suspend_vm(e)
elif request_event.type == RequestType.StopVM:
stop_vm(vm_entry)
elif e.status == "REQUESTED_RESUME":
resume_vm(e)
elif request_event.type == RequestType.DeleteVM:
delete_vm(vm_entry)
elif e.status == "REQUESTED_START":
start_vm(e)
elif request_event.type == RequestType.InitVMMigration:
init_vm_migration(vm_entry, host.key)
elif e.status == "REQUESTED_SHUTDOWN":
shutdown_vm(e)
elif e.status == "DELETED":
delete_vm(e)
elif request_event.type == RequestType.TransferVM:
transfer_vm(request_event)
logging.info(f"Running VMs {running_vms}")