diff --git a/api/helper.py b/api/helper.py
index a45bd16..eb32373 100755
--- a/api/helper.py
+++ b/api/helper.py
@@ -22,7 +22,7 @@ def check_otp(name, realm, token):
except binascii.Error:
return 400
- response = requests.get(
+ response = requests.post(
"{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
OTP_SERVER=env_vars.get("OTP_SERVER", ""),
OTP_VERIFY_ENDPOINT=env_vars.get("OTP_VERIFY_ENDPOINT", "verify"),
diff --git a/api/main.py b/api/main.py
index e621ce1..59b7dc0 100644
--- a/api/main.py
+++ b/api/main.py
@@ -1,16 +1,16 @@
import json
-import os
import subprocess
-from uuid import uuid4
-
import pynetbox
+
+from uuid import uuid4
+from os.path import join as join_path
+
from flask import Flask, request
from flask_restful import Resource, Api
from common import counters
from common.request import RequestEntry, RequestType
-from common.vm import VMStatus
-from config import (etcd_client, request_pool, vm_pool, host_pool, env_vars)
+from config import (etcd_client, request_pool, vm_pool, host_pool, env_vars, image_storage_handler)
from . import schemas
from .helper import generate_mac, mac2ipv6
from api import logger
@@ -20,13 +20,15 @@ api = Api(app)
class CreateVM(Resource):
+ """API Request to Handle Creation of VM"""
+
@staticmethod
def post():
data = request.json
validator = schemas.CreateVMSchema(data)
if validator.is_valid():
vm_uuid = uuid4().hex
- vm_key = os.path.join(env_vars.get("VM_PREFIX"), vm_uuid)
+ vm_key = join_path(env_vars.get("VM_PREFIX"), vm_uuid)
specs = {
"cpu": validator.specs["cpu"],
"ram": validator.specs["ram"],
@@ -67,14 +69,14 @@ class VmStatus(Resource):
validator = schemas.VMStatusSchema(data)
if validator.is_valid():
vm = vm_pool.get(
- os.path.join(env_vars.get("VM_PREFIX"), data["uuid"])
+ join_path(env_vars.get("VM_PREFIX"), data["uuid"])
)
vm_value = vm.value.copy()
vm_value["ip"] = []
for network_and_mac in vm.network:
network_name, mac = network_and_mac
network = etcd_client.get(
- os.path.join(
+ join_path(
env_vars.get("NETWORK_PREFIX"),
data["name"],
network_name,
@@ -96,7 +98,7 @@ class CreateImage(Resource):
validator = schemas.CreateImageSchema(data)
if validator.is_valid():
file_entry = etcd_client.get(
- os.path.join(env_vars.get("FILE_PREFIX"), data["uuid"])
+ join_path(env_vars.get("FILE_PREFIX"), data["uuid"])
)
file_entry_value = json.loads(file_entry.value)
@@ -109,7 +111,7 @@ class CreateImage(Resource):
"visibility": "public",
}
etcd_client.put(
- os.path.join(env_vars.get("IMAGE_PREFIX"), data["uuid"]),
+ join_path(env_vars.get("IMAGE_PREFIX"), data["uuid"]),
json.dumps(image_entry_json),
)
@@ -123,8 +125,9 @@ class ListPublicImages(Resource):
images = etcd_client.get_prefix(
env_vars.get("IMAGE_PREFIX"), value_in_json=True
)
- r = {}
- r["images"] = []
+ r = {
+ "images": []
+ }
for image in images:
image_key = "{}:{}".format(
image.value["store_name"], image.value["name"]
@@ -143,46 +146,22 @@ class VMAction(Resource):
if validator.is_valid():
vm_entry = vm_pool.get(
- os.path.join(env_vars.get("VM_PREFIX"), data["uuid"])
+ join_path(env_vars.get("VM_PREFIX"), data["uuid"])
)
action = data["action"]
if action == "start":
- vm_entry.status = VMStatus.requested_start
- vm_pool.put(vm_entry)
action = "schedule"
if action == "delete" and vm_entry.hostname == "":
- try:
- path_without_protocol = vm_entry.path[
- vm_entry.path.find(":") + 1:
- ]
-
- if env_vars.get("WITHOUT_CEPH"):
- command_to_delete = [
- "rm",
- "-rf",
- os.path.join("/var/vm", vm_entry.uuid),
- ]
- else:
- command_to_delete = [
- "rbd",
- "rm",
- path_without_protocol,
- ]
-
- subprocess.check_output(
- command_to_delete, stderr=subprocess.PIPE
- )
- except subprocess.CalledProcessError as e:
- if "No such file" in e.stderr.decode("utf-8"):
+ if image_storage_handler.is_vm_image_exists(vm_entry.uuid):
+ r_status = image_storage_handler.delete_vm_image(vm_entry.uuid)
+ if r_status:
etcd_client.client.delete(vm_entry.key)
return {"message": "VM successfully deleted"}
else:
- logger.exception(e)
- return {
- "message": "Some error occurred while deleting VM"
- }
+ logger.error("Some Error Occurred while deleting VM")
+ return {"message": "VM deletion unsuccessfull"}
else:
etcd_client.client.delete(vm_entry.key)
return {"message": "VM successfully deleted"}
@@ -211,8 +190,8 @@ class VMMigration(Resource):
r = RequestEntry.from_scratch(
type=RequestType.ScheduleVM,
uuid=vm.uuid,
- destination=os.path.join(
- env_vars.get("HOST_PREFIX"), data["destination"]
+ destination=join_path(
+ env_vars.get("HOST_PREFIX"), validator.destination.value
),
migration=True,
request_prefix=env_vars.get("REQUEST_PREFIX")
@@ -289,7 +268,7 @@ class CreateHost(Resource):
data = request.json
validator = schemas.CreateHostSchema(data)
if validator.is_valid():
- host_key = os.path.join(env_vars.get("HOST_PREFIX"), uuid4().hex)
+ host_key = join_path(env_vars.get("HOST_PREFIX"), uuid4().hex)
host_entry = {
"specs": data["specs"],
"hostname": data["hostname"],
@@ -327,7 +306,7 @@ class GetSSHKeys(Resource):
if not validator.key_name.value:
# {user_prefix}/{realm}/{name}/key/
- etcd_key = os.path.join(
+ etcd_key = join_path(
env_vars.get('USER_PREFIX'),
data["realm"],
data["name"],
@@ -344,7 +323,7 @@ class GetSSHKeys(Resource):
else:
# {user_prefix}/{realm}/{name}/key/{key_name}
- etcd_key = os.path.join(
+ etcd_key = join_path(
env_vars.get('USER_PREFIX'),
data["realm"],
data["name"],
@@ -373,7 +352,7 @@ class AddSSHKey(Resource):
if validator.is_valid():
# {user_prefix}/{realm}/{name}/key/{key_name}
- etcd_key = os.path.join(
+ etcd_key = join_path(
env_vars.get("USER_PREFIX"),
data["realm"],
data["name"],
@@ -403,7 +382,7 @@ class RemoveSSHKey(Resource):
if validator.is_valid():
# {user_prefix}/{realm}/{name}/key/{key_name}
- etcd_key = os.path.join(
+ etcd_key = join_path(
env_vars.get("USER_PREFIX"),
data["realm"],
data["name"],
@@ -462,7 +441,7 @@ class CreateNetwork(Resource):
else:
network_entry["ipv6"] = "fd00::/64"
- network_key = os.path.join(
+ network_key = join_path(
env_vars.get("NETWORK_PREFIX"),
data["name"],
data["network_name"],
@@ -480,7 +459,7 @@ class ListUserNetwork(Resource):
validator = schemas.OTPSchema(data)
if validator.is_valid():
- prefix = os.path.join(
+ prefix = join_path(
env_vars.get("NETWORK_PREFIX"), data["name"]
)
networks = etcd_client.get_prefix(prefix, value_in_json=True)
@@ -517,15 +496,17 @@ api.add_resource(CreateNetwork, "/network/create")
def main():
- data = {
- "is_public": True,
- "type": "ceph",
- "name": "images",
- "description": "first ever public image-store",
- "attributes": {"list": [], "key": [], "pool": "images"},
- }
+ image_stores = list(etcd_client.get_prefix(env_vars.get('IMAGE_STORE_PREFIX'), value_in_json=True))
+ if len(image_stores) == 0:
+ data = {
+ "is_public": True,
+ "type": "ceph",
+ "name": "images",
+ "description": "first ever public image-store",
+ "attributes": {"list": [], "key": [], "pool": "images"},
+ }
- etcd_client.put(os.path.join(env_vars.get('IMAGE_STORE_PREFIX'), uuid4().hex), json.dumps(data))
+ etcd_client.put(join_path(env_vars.get('IMAGE_STORE_PREFIX'), uuid4().hex), json.dumps(data))
app.run(host="::", debug=True)
diff --git a/api/schemas.py b/api/schemas.py
index 28a1bc1..e50d9f0 100755
--- a/api/schemas.py
+++ b/api/schemas.py
@@ -381,12 +381,14 @@ class VmMigrationSchema(OTPSchema):
super().__init__(data=data, fields=fields)
def destination_validation(self):
- host_key = self.destination.value
- host = host_pool.get(host_key)
+ hostname = self.destination.value
+ host = next(filter(lambda h: h.hostname == hostname, host_pool.hosts), None)
if not host:
self.add_error("No Such Host ({}) exists".format(self.destination.value))
elif host.status != HostStatus.alive:
self.add_error("Destination Host is dead")
+ else:
+ self.destination.value = host.key
def validation(self):
vm = vm_pool.get(self.uuid.value)
diff --git a/common/classes.py b/common/classes.py
index 2cea033..2eae809 100644
--- a/common/classes.py
+++ b/common/classes.py
@@ -1,28 +1,6 @@
-from decouple import Config, RepositoryEnv, UndefinedValueError
from etcd3_wrapper import EtcdEntry
-class EnvironmentVariables:
- def __init__(self, env_file):
- try:
- env_config = Config(RepositoryEnv(env_file))
- except FileNotFoundError:
- print("{} does not exists".format(env_file))
- exit(1)
- else:
- self.config = env_config
-
- def get(self, *args, **kwargs):
- """Return value of var from env_vars"""
- try:
- value = self.config.get(*args, **kwargs)
- except UndefinedValueError as e:
- print(e)
- exit(1)
- else:
- return value
-
-
class SpecificEtcdEntryBase:
def __init__(self, e: EtcdEntry):
self.key = e.key
diff --git a/common/helpers.py b/common/helpers.py
index c0d64e4..1bdf0b4 100644
--- a/common/helpers.py
+++ b/common/helpers.py
@@ -1,5 +1,9 @@
import logging
import socket
+import requests
+import json
+
+from ipaddress import ip_address
from os.path import join as join_path
@@ -37,3 +41,14 @@ def get_ipv4_address():
address = s.getsockname()[0]
return address
+
+
+def get_ipv6_address():
+ try:
+ r = requests.get("https://api6.ipify.org?format=json")
+ content = json.loads(r.content.decode("utf-8"))
+ ip = ip_address(content["ip"]).exploded
+ except Exception as e:
+ logging.exception(e)
+ else:
+ return ip
diff --git a/common/storage_handlers.py b/common/storage_handlers.py
new file mode 100644
index 0000000..c74bca8
--- /dev/null
+++ b/common/storage_handlers.py
@@ -0,0 +1,158 @@
+import shutil
+import subprocess as sp
+import os
+import stat
+
+from abc import ABC
+from host import logger
+from os.path import join as join_path
+
+
+class ImageStorageHandler(ABC):
+ def __init__(self, image_base, vm_base):
+ self.image_base = image_base
+ self.vm_base = vm_base
+
+ def import_image(self, image_src, image_dest, protect=False):
+ """Put an image at the destination
+ :param src: An Image file
+ :param dest: A path where :param src: is to be put.
+ :param protect: If protect is true then the dest is protect (readonly etc)
+ The obj must exist on filesystem.
+ """
+
+ raise NotImplementedError()
+
+ def make_vm_image(self, image_path, path):
+ """Copy image from src to dest
+
+ :param src: A path
+ :param dest: A path
+
+ src and destination must be on same storage system i.e both on file system or both on CEPH etc.
+ """
+ raise NotImplementedError()
+
+ def resize_vm_image(self, path, size):
+ """Resize image located at :param path:
+ :param path: The file which is to be resized
+ :param size: Size must be in Megabytes
+ """
+ raise NotImplementedError()
+
+ def delete_vm_image(self, path):
+ raise NotImplementedError()
+
+ def execute_command(self, command, report=True):
+ command = list(map(str, command))
+ try:
+ output = sp.check_output(command, stderr=sp.PIPE)
+ except Exception as e:
+ if report:
+ print(e)
+ logger.exception(e)
+ return False
+ return True
+
+ def vm_path_string(self, path):
+ raise NotImplementedError()
+
+ def qemu_path_string(self, path):
+ raise NotImplementedError()
+
+ def is_vm_image_exists(self, path):
+ raise NotImplementedError()
+
+
+class FileSystemBasedImageStorageHandler(ImageStorageHandler):
+ def import_image(self, src, dest, protect=False):
+ dest = join_path(self.image_base, dest)
+ try:
+ shutil.copy(src, dest)
+ if protect:
+ os.chmod(dest, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
+ except Exception as e:
+ logger.exception(e)
+ return False
+ return True
+
+ def make_vm_image(self, src, dest):
+ src = join_path(self.image_base, src)
+ dest = join_path(self.vm_base, dest)
+ try:
+ shutil.copy(src, dest)
+ except Exception as e:
+ logger.exception(e)
+ return False
+ return True
+
+ def resize_vm_image(self, path, size):
+ path = join_path(self.vm_base, path)
+ command = ["qemu-img", "resize", "-f", "raw", path, "{}M".format(size)]
+ if self.execute_command(command):
+ return True
+ else:
+ self.delete_vm_image(path)
+ return False
+
+ def delete_vm_image(self, path):
+ path = join_path(self.vm_base, path)
+ try:
+ os.remove(path)
+ except Exception as e:
+ logger.exception(e)
+ return False
+ return True
+
+ def vm_path_string(self, path):
+ return join_path(self.vm_base, path)
+
+ def qemu_path_string(self, path):
+ return self.vm_path_string(path)
+
+ def is_vm_image_exists(self, path):
+ path = join_path(self.vm_base, path)
+ command = ["ls", path]
+ return self.execute_command(command, report=False)
+
+
+class CEPHBasedImageStorageHandler(ImageStorageHandler):
+ def import_image(self, src, dest, protect=False):
+ dest = join_path(self.image_base, dest)
+ command = ["rbd", "import", src, dest]
+ if protect:
+ snap_create_command = ["rbd", "snap", "create", "{}@protected".format(dest)]
+ snap_protect_command = ["rbd", "snap", "protect", "{}@protected".format(dest)]
+
+ return self.execute_command(command) and self.execute_command(snap_create_command) and\
+ self.execute_command(snap_protect_command)
+
+ return self.execute_command(command)
+
+ def make_vm_image(self, src, dest):
+ src = join_path(self.image_base, src)
+ dest = join_path(self.vm_base, dest)
+
+ command = ["rbd", "clone", "{}@protected".format(src), dest]
+ return self.execute_command(command)
+
+ def resize_vm_image(self, path, size):
+ path = join_path(self.vm_base, path)
+ command = ["rbd", "resize", path, "--size", size]
+ return self.execute_command(command)
+
+ def delete_vm_image(self, path):
+ path = join_path(self.vm_base, path)
+ command = ["rbd", "rm", path]
+ return self.execute_command(command)
+
+ def vm_path_string(self, path):
+ return join_path(self.vm_base, path)
+
+ def qemu_path_string(self, path):
+ return "rbd:{}".format(self.vm_path_string(path))
+
+ def is_vm_image_exists(self, path):
+ path = join_path(self.vm_base, path)
+ command = ["rbd", "info", path]
+ return self.execute_command(command, report=False)
diff --git a/common/vm.py b/common/vm.py
index c778fac..c1c1928 100644
--- a/common/vm.py
+++ b/common/vm.py
@@ -60,10 +60,6 @@ class VMEntry(SpecificEtcdEntryBase):
self.log = self.log[:5]
self.log.append("{} - {}".format(datetime.now().isoformat(), msg))
- @property
- def path(self):
- return "rbd:uservms/{}".format(self.uuid)
-
class VmPool:
def __init__(self, etcd_client, vm_prefix):
diff --git a/config.py b/config.py
index 5729fed..1048320 100644
--- a/config.py
+++ b/config.py
@@ -1,14 +1,16 @@
from etcd3_wrapper import Etcd3Wrapper
-from common.classes import EnvironmentVariables
from common.host import HostPool
from common.request import RequestPool
from common.vm import VmPool
+from common.storage_handlers import FileSystemBasedImageStorageHandler, CEPHBasedImageStorageHandler
+from decouple import Config, RepositoryEnv
-env_vars = EnvironmentVariables('/etc/ucloud/ucloud.conf')
+
+env_vars = Config(RepositoryEnv('/etc/ucloud/ucloud.conf'))
etcd_wrapper_args = ()
-etcd_wrapper_kwargs = {"host": env_vars.get("ETCD_URL")}
+etcd_wrapper_kwargs = {'host': env_vars.get('ETCD_URL')}
etcd_client = Etcd3Wrapper(*etcd_wrapper_args, **etcd_wrapper_kwargs)
@@ -17,3 +19,12 @@ vm_pool = VmPool(etcd_client, env_vars.get('VM_PREFIX'))
request_pool = RequestPool(etcd_client, env_vars.get('REQUEST_PREFIX'))
running_vms = []
+
+__storage_backend = env_vars.get("STORAGE_BACKEND")
+if __storage_backend == "filesystem":
+ image_storage_handler = FileSystemBasedImageStorageHandler(vm_base=env_vars.get("VM_DIR"),
+ image_base=env_vars.get("IMAGE_DIR"))
+elif __storage_backend == "ceph":
+ image_storage_handler = CEPHBasedImageStorageHandler(vm_base="ssd", image_base="ssd")
+else:
+ raise Exception("Unknown Image Storage Handler")
diff --git a/docs/source/diagram-code/ucloud b/docs/source/diagram-code/ucloud
new file mode 100644
index 0000000..5e73b3d
--- /dev/null
+++ b/docs/source/diagram-code/ucloud
@@ -0,0 +1,44 @@
+graph LR
+ style ucloud fill:#FFD2FC
+ style cron fill:#FFF696
+ style infrastructure fill:#BDF0FF
+ subgraph ucloud[ucloud]
+ ucloud-cli[CLI]-->ucloud-api[API]
+ ucloud-api-->ucloud-scheduler[Scheduler]
+ ucloud-api-->ucloud-imagescanner[Image Scanner]
+ ucloud-api-->ucloud-host[Host]
+ ucloud-scheduler-->ucloud-host
+
+ ucloud-host-->need-networking{VM need Networking}
+ need-networking-->|Yes| networking-scripts
+ need-networking-->|No| VM[Virtual Machine]
+ need-networking-->|SLAAC?| radvd
+ networking-scripts-->VM
+ networking-scripts--Create Networks Devices-->networking-scripts
+ subgraph cron[Cron Jobs]
+ ucloud-imagescanner
+ ucloud-filescanner[File Scanner]
+ ucloud-filescanner--Track User files-->ucloud-filescanner
+ end
+ subgraph infrastructure[Infrastructure]
+ radvd
+ etcd
+ networking-scripts[Networking Scripts]
+ ucloud-imagescanner-->image-store
+ image-store{Image Store}
+ image-store-->|CEPH| ceph
+ image-store-->|FILE| file-system
+ ceph[CEPH]
+ file-system[File System]
+ end
+subgraph virtual-machine[Virtual Machine]
+ VM
+ VM-->ucloud-init
+
+end
+
+subgraph metadata-group[Metadata Server]
+metadata-->ucloud-init
+ucloud-init<-->metadata
+end
+end
diff --git a/docs/source/images/ucloud.svg b/docs/source/images/ucloud.svg
new file mode 100644
index 0000000..f7e33f8
--- /dev/null
+++ b/docs/source/images/ucloud.svg
@@ -0,0 +1,494 @@
+
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 0307de8..6443af1 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,7 +1,7 @@
.. ucloud documentation master file, created by
-sphinx-quickstart on Mon Nov 11 19:08:16 2019.
-You can adapt this file completely to your liking, but it should at least
-contain the root `toctree` directive.
+ sphinx-quickstart on Mon Nov 11 19:08:16 2019.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
Welcome to ucloud's documentation!
==================================
@@ -15,7 +15,9 @@ Welcome to ucloud's documentation!
usage/usage-for-admins
usage/usage-for-users
usage/how-to-create-an-os-image-for-ucloud
+ theory/summary
misc/todo
+ troubleshooting/installation-troubleshooting
Indices and tables
==================
diff --git a/docs/source/introduction/installation.rst b/docs/source/introduction/installation.rst
index b271ab9..0f36714 100644
--- a/docs/source/introduction/installation.rst
+++ b/docs/source/introduction/installation.rst
@@ -135,7 +135,7 @@ You just need to update **AUTH_SEED** in the below code to match your auth's see
ETCD_URL=localhost
- WITHOUT_CEPH=True
+ STORAGE_BACKEND=filesystem
BASE_DIR=/var/www
IMAGE_DIR=/var/image
@@ -195,3 +195,35 @@ profile e.g *~/.profile*
alias uotp='cd /root/uotp/ && pipenv run python app.py'
and run :code:`source ~/.profile`
+
+
+Arch
+-----
+
+.. code-block:: sh
+
+ # Update/Upgrade
+ pacman -Syuu
+ pacman -S python3 qemu chrony python-pip
+
+ pip3 install pipenv
+
+ cat > /etc/chrony.conf << EOF
+ server 0.arch.pool.ntp.org
+ server 1.arch.pool.ntp.org
+ server 2.arch.pool.ntp.org
+ EOF
+
+ systemctl start chronyd
+ systemctl enable chronyd
+
+ # Create non-root user and allow it sudo access
+ # without password
+ useradd -m ucloud
+ echo "ucloud ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+
+ sudo -H -u ucloud bash -c 'cd /home/ucloud && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si'
+ sudo -H -u ucloud bash -c 'yay -S etcd'
+
+ systemctl start etcd
+ systemctl enable etcd
\ No newline at end of file
diff --git a/docs/source/misc/todo.rst b/docs/source/misc/todo.rst
index 3b85e89..4f7fde4 100644
--- a/docs/source/misc/todo.rst
+++ b/docs/source/misc/todo.rst
@@ -1,6 +1,18 @@
TODO
====
+* **Check Authentication:** Nico reported that some endpoints
+ even work without providing token. (ListUserVM)
+
+* Put overrides for **IMAGE_BASE**, **VM_BASE** in **ImageStorageHandler**.
+
+* Put "Always use only one StorageHandler"
+
+* Create Network Manager
+ * That would handle tasks like up/down an interface
+ * Create VXLANs, Bridges, TAPs.
+ * Remove them when they are no longer used.
+
* Check for :code:`etcd3.exceptions.ConnectionFailedError` when calling some etcd operation to
avoid crashing whole application.
* Throw KeyError instead of returning None when some key is not found in etcd.
diff --git a/docs/source/theory/summary.rst b/docs/source/theory/summary.rst
new file mode 100644
index 0000000..62f6200
--- /dev/null
+++ b/docs/source/theory/summary.rst
@@ -0,0 +1,98 @@
+Summary
+=======
+
+.. image:: /images/ucloud.svg
+
+.. code-block::
+
+
+ |
+ |
+ |
+ +-------------------------
+ | |
+ | |```````````````|```````````````|
+ | | | |
+ |
+ | |
+ | |
+ +-------------------------
+ |
+ |
+ |
+ Virtual Machine------------
+
+
+
+**ucloud-cli** interact with **ucloud-api** to do the following operations:
+
+- Create/Delete/Start/Stop/Migrate/Probe (Status of) Virtual Machines
+- Create/Delete Networks
+- Add/Get/Delete SSH Keys
+- Create OS Image out of a file (tracked by file_scanner)
+- List User's files/networks/vms
+- Add Host
+
+ucloud can currently stores OS-Images on
+
+* File System
+* `CEPH `_
+
+
+**ucloud-api** in turns creates appropriate Requests which are taken
+by suitable components of ucloud. For Example, if user uses ucloud-cli
+to create a VM, **ucloud-api** would create a **ScheduleVMRequest** containing
+things like pointer to VM's entry which have specs, networking
+configuration of VMs.
+
+**ucloud-scheduler** accepts requests for VM's scheduling and
+migration. It finds a host from a list of available host on which
+the incoming VM can run and schedules it on that host.
+
+**ucloud-host** runs on host servers i.e servers that
+actually runs virtual machines, accepts requests
+intended only for them. It creates/delete/start/stop/migrate
+virtual machines. It also arrange network resources needed for the
+incoming VM.
+
+**ucloud-filescanner** keep tracks of user's files which would be needed
+later for creating OS Images.
+
+**ucloud-imagescanner** converts images files from qcow2 format to raw
+format which would then be imported into image store.
+
+* In case of **File System**, the converted image would be copied to
+ :file:`/var/image/` or the path referred by :envvar:`IMAGE_PATH` environement variable
+ mentioned in :file:`/etc/ucloud/ucloud.conf`.
+
+* In case of **CEPH**, the converted image would be imported into
+ specific pool (it depends on the image store in which the image
+ belongs) of CEPH Block Storage.
+
+**ucloud-metadata** provides metadata which is used to contextualize
+VMs. When, the VM is created, it is just clone (duplicate) of OS
+image from which it is created. So, to differentiate between my
+VM and your VM, the VM need to be contextualized. This works
+like the following
+
+.. note::
+ Actually, ucloud-init makes the GET request. You can also try it
+ yourself using curl but ucloud-init does that for yourself.
+
+* VM make a GET requests http://metadata which resolves to actual
+ address of metadata server. The metadata server looks at the IPv6
+ Address of the requester and extracts the MAC Address which is possible
+ because the IPv6 address is
+ `IPv6 EUI-64 `_.
+ Metadata use this MAC address to find the actual VM to which it belongs
+ and its owner, ssh-keys and much more. Then, metadata return these
+ details back to the calling VM in JSON format. These details are
+ then used be the **ucloud-init** which is explained next.
+
+**ucloud-init** gets the metadata from **ucloud-metadata** to contextualize
+the VM. Specifically, it gets owner's ssh keys (or any other keys the
+owner of VM added to authorized keys for this VM) and put them to ssh
+server's (installed on VM) authorized keys so that owner can access
+the VM using ssh. It also install softwares that are needed for correct
+behavior of VM e.g rdnssd (needed for `SLAAC `_).
+
diff --git a/docs/source/troubleshooting/installation-troubleshooting.rst b/docs/source/troubleshooting/installation-troubleshooting.rst
new file mode 100644
index 0000000..4d9dda4
--- /dev/null
+++ b/docs/source/troubleshooting/installation-troubleshooting.rst
@@ -0,0 +1,24 @@
+Installation Troubleshooting
+============================
+
+etcd doesn't start
+------------------
+
+.. code-block:: sh
+
+ [root@archlinux ~]# systemctl start etcd
+ Job for etcd.service failed because the control process exited with error code.
+ See "systemctl status etcd.service" and "journalctl -xe" for details
+
+possible solution
+~~~~~~~~~~~~~~~~~
+Try :code:`cat /etc/hosts` if its output contain the following
+
+.. code-block:: sh
+
+ 127.0.0.1 localhost.localdomain localhost
+ ::1 localhost localhost.localdomain
+
+
+then unfortunately, we can't help you. But, if it doesn't contain the
+above you can put the above in :file:`/etc/hosts` to fix the issue.
diff --git a/filescanner/__init__.py b/filescanner/__init__.py
index e69de29..eea436a 100644
--- a/filescanner/__init__.py
+++ b/filescanner/__init__.py
@@ -0,0 +1,3 @@
+import logging
+
+logger = logging.getLogger(__name__)
diff --git a/filescanner/main.py b/filescanner/main.py
index d1ffa46..b80169c 100755
--- a/filescanner/main.py
+++ b/filescanner/main.py
@@ -6,7 +6,7 @@ import time
from uuid import uuid4
from etcd3_wrapper import Etcd3Wrapper
-
+from filescanner import logger
from config import env_vars
@@ -17,9 +17,10 @@ def getxattr(file, attr):
value = sp.check_output(['getfattr', file,
'--name', attr,
'--only-values',
- '--absolute-names'])
+ '--absolute-names'], stderr=sp.DEVNULL)
value = value.decode("utf-8")
- except sp.CalledProcessError:
+ except sp.CalledProcessError as e:
+ logger.exception(e)
value = None
return value
@@ -63,7 +64,7 @@ try:
sp.check_output(['which', 'getfattr'])
sp.check_output(['which', 'setfattr'])
except Exception as e:
- print(e)
+ logger.exception(e)
print('Make sure you have getfattr and setfattr available')
exit(1)
diff --git a/host/helper.py b/host/helper.py
new file mode 100644
index 0000000..edcb82d
--- /dev/null
+++ b/host/helper.py
@@ -0,0 +1,13 @@
+import socket
+from contextlib import closing
+
+
+def find_free_port():
+ with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
+ try:
+ s.bind(('', 0))
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ except Exception:
+ return None
+ else:
+ return s.getsockname()[1]
diff --git a/host/main.py b/host/main.py
index 5b5e620..f512fee 100755
--- a/host/main.py
+++ b/host/main.py
@@ -1,6 +1,5 @@
import argparse
import multiprocessing as mp
-import os
import time
from etcd3_wrapper import Etcd3Wrapper
@@ -10,13 +9,17 @@ from config import (vm_pool, request_pool,
etcd_client, running_vms,
etcd_wrapper_args, etcd_wrapper_kwargs,
HostPool, env_vars)
+
+from .helper import find_free_port
from . import virtualmachine
from host import logger
-def update_heartbeat(host):
+
+def update_heartbeat(hostname):
+ """Update Last HeartBeat Time for :param hostname: in etcd"""
client = Etcd3Wrapper(*etcd_wrapper_args, **etcd_wrapper_kwargs)
host_pool = HostPool(client, env_vars.get('HOST_PREFIX'))
- this_host = next(filter(lambda h: h.hostname == host, host_pool.hosts), None)
+ this_host = next(filter(lambda h: h.hostname == hostname, host_pool.hosts), None)
while True:
this_host.update_heartbeat()
@@ -35,17 +38,22 @@ def maintenance(host):
# whether this host vm is successfully migrated. If yes
# then we shutdown "vm1" on this host.
+ to_be_removed = []
for running_vm in running_vms:
with vm_pool.get_put(running_vm.key) as vm_entry:
if vm_entry.hostname != host.key and not vm_entry.in_migration:
running_vm.handle.shutdown()
- vm_entry.add_log("VM on source host shutdown.")
+ logger.info("VM migration not completed successfully.")
+ to_be_removed.append(running_vm)
+
+ for r in to_be_removed:
+ running_vms.remove(r)
+
# 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 = virtualmachine.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
@@ -64,10 +72,6 @@ def maintenance(host):
def main(hostname):
- assert env_vars.get('WITHOUT_CEPH') and os.path.isdir(env_vars.get('VM_DIR')), (
- "You have set env_vars.get('WITHOUT_CEPH') to True. So, the vm directory mentioned"
- " in .env file must exists. But, it don't.")
-
heartbeat_updating_process = mp.Process(target=update_heartbeat, args=(hostname,))
host_pool = HostPool(etcd_client, env_vars.get('HOST_PREFIX'))
@@ -99,7 +103,6 @@ def main(hostname):
request_event = RequestEntry(request_event)
if request_event.type == "TIMEOUT":
- logger.info("Timeout Event")
maintenance(host)
continue
@@ -121,7 +124,7 @@ def main(hostname):
virtualmachine.delete(vm_entry)
elif request_event.type == RequestType.InitVMMigration:
- virtualmachine.init_migration(vm_entry, host.key)
+ virtualmachine.start(vm_entry, host.key, find_free_port())
elif request_event.type == RequestType.TransferVM:
virtualmachine.transfer(request_event)
diff --git a/host/qmp/__init__.py b/host/qmp/__init__.py
index ba15838..775b397 100755
--- a/host/qmp/__init__.py
+++ b/host/qmp/__init__.py
@@ -304,6 +304,7 @@ class QEMUMachine(object):
LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
if self._iolog:
LOG.debug('Output: %r', self._iolog)
+ raise Exception(self._iolog)
raise
def _launch(self):
diff --git a/host/virtualmachine.py b/host/virtualmachine.py
index 80e9846..5000410 100755
--- a/host/virtualmachine.py
+++ b/host/virtualmachine.py
@@ -4,27 +4,28 @@
# For QEMU Monitor Protocol Commands Information, See
# https://qemu.weilnetz.de/doc/qemu-doc.html#pcsys_005fmonitor
-import errno
import os
import random
import subprocess as sp
import tempfile
import time
+
from functools import wraps
-from os.path import join
+from os.path import join as join_path
from string import Template
from typing import Union
import bitmath
import sshtunnel
-from common.helpers import get_ipv4_address
+from common.helpers import get_ipv6_address
from common.request import RequestEntry, RequestType
from common.vm import VMEntry, VMStatus
-from config import etcd_client, request_pool, running_vms, vm_pool, env_vars
+from config import etcd_client, request_pool, running_vms, vm_pool, env_vars, image_storage_handler
from . import qmp
from host import logger
+
class VM:
def __init__(self, key, handle, vnc_socket_file):
self.key = key # type: str
@@ -106,24 +107,16 @@ def update_radvd_conf(etcd_client):
sp.check_output(['systemctl', 'restart', 'radvd'])
-def get_start_command_args(
- vm_entry, vnc_sock_filename: str, migration=False, migration_port=4444,
-):
+def get_start_command_args(vm_entry, vnc_sock_filename: str, migration=False, migration_port=None):
threads_per_core = 1
- vm_memory = int(bitmath.parse_string(vm_entry.specs["ram"]).to_MB())
+ vm_memory = int(bitmath.parse_string_unsafe(vm_entry.specs["ram"]).to_MB())
vm_cpus = int(vm_entry.specs["cpu"])
vm_uuid = vm_entry.uuid
vm_networks = vm_entry.network
- if env_vars.get('WITHOUT_CEPH'):
- command = "-drive file={},format=raw,if=virtio,cache=none".format(
- os.path.join(env_vars.get('VM_DIR'), vm_uuid)
- )
- else:
- command = "-drive file=rbd:uservms/{},format=raw,if=virtio,cache=none".format(
- vm_uuid
- )
-
+ command = "-drive file={},format=raw,if=virtio,cache=none".format(
+ image_storage_handler.qemu_path_string(vm_uuid)
+ )
command += " -device virtio-rng-pci -vnc unix:{}".format(vnc_sock_filename)
command += " -m {} -smp cores={},threads={}".format(
vm_memory, vm_cpus, threads_per_core
@@ -131,7 +124,7 @@ def get_start_command_args(
command += " -name {}".format(vm_uuid)
if migration:
- command += " -incoming tcp:0:{}".format(migration_port)
+ command += " -incoming tcp:[::]:{}".format(migration_port)
tap = None
for network_and_mac in vm_networks:
@@ -154,7 +147,7 @@ def get_start_command_args(
return command.split(" ")
-def create_vm_object(vm_entry, migration=False, migration_port=4444):
+def create_vm_object(vm_entry, migration=False, migration_port=None):
# NOTE: If migration suddenly stop working, having different
# VNC unix filename on source and destination host can
# be a possible cause of it.
@@ -198,61 +191,19 @@ def need_running_vm(func):
def create(vm_entry: VMEntry):
- vm_hdd = int(bitmath.parse_string(vm_entry.specs["os-ssd"]).to_MB())
-
- if env_vars.get('WITHOUT_CEPH'):
- _command_to_create = [
- "cp",
- os.path.join(env_vars.get('IMAGE_DIR'), vm_entry.image_uuid),
- os.path.join(env_vars.get('VM_DIR'), vm_entry.uuid),
- ]
-
- _command_to_extend = [
- "qemu-img",
- "resize",
- "-f", "raw",
- os.path.join(env_vars.get('VM_DIR'), vm_entry.uuid),
- "{}M".format(vm_hdd),
- ]
+ if image_storage_handler.is_vm_image_exists(vm_entry.uuid):
+ # File Already exists. No Problem Continue
+ logger.debug("Image for vm %s exists", vm_entry.uuid)
else:
- _command_to_create = [
- "rbd",
- "clone",
- "images/{}@protected".format(vm_entry.image_uuid),
- "uservms/{}".format(vm_entry.uuid),
- ]
-
- _command_to_extend = [
- "rbd",
- "resize",
- "uservms/{}".format(vm_entry.uuid),
- "--size",
- vm_hdd,
- ]
-
- try:
- sp.check_output(_command_to_create)
- except sp.CalledProcessError as e:
- if e.returncode == errno.EEXIST:
- logger.debug("Image for vm %s exists", vm_entry.uuid)
- # File Already exists. No Problem Continue
- return
-
- # This exception catches all other exceptions
- # i.e FileNotFound (BaseImage), pool Does Not Exists etc.
- logger.exception(e)
-
- vm_entry.status = "ERROR"
- else:
- try:
- sp.check_output(_command_to_extend)
- except Exception as e:
- logger.exception(e)
- else:
- logger.info("New VM Created")
+ vm_hdd = int(bitmath.parse_string_unsafe(vm_entry.specs["os-ssd"]).to_MB())
+ if image_storage_handler.make_vm_image(src=vm_entry.image_uuid, dest=vm_entry.uuid):
+ if not image_storage_handler.resize_vm_image(path=vm_entry.uuid, size=vm_hdd):
+ vm_entry.status = "ERROR"
+ else:
+ logger.info("New VM Created")
-def start(vm_entry: VMEntry):
+def start(vm_entry: VMEntry, destination_host_key=None, migration_port=None):
_vm = get_vm(running_vms, vm_entry.key)
# VM already running. No need to proceed further.
@@ -260,8 +211,12 @@ def start(vm_entry: VMEntry):
logger.info("VM %s already running", vm_entry.uuid)
return
else:
- create(vm_entry)
- launch_vm(vm_entry)
+ if destination_host_key:
+ launch_vm(vm_entry, migration=True, migration_port=migration_port,
+ destination_host_key=destination_host_key)
+ else:
+ create(vm_entry)
+ launch_vm(vm_entry)
@need_running_vm
@@ -278,18 +233,9 @@ def stop(vm_entry):
def delete(vm_entry):
logger.info("Deleting VM | %s", vm_entry)
stop(vm_entry)
- path_without_protocol = vm_entry.path[vm_entry.path.find(":") + 1:]
- if env_vars.get('WITHOUT_CEPH'):
- vm_deletion_command = ["rm", os.path.join(env_vars.get('VM_DIR'), vm_entry.uuid)]
- else:
- vm_deletion_command = ["rbd", "rm", path_without_protocol]
-
- try:
- sp.check_output(vm_deletion_command)
- except Exception as e:
- logger.exception(e)
- else:
+ r_status = image_storage_handler.delete_vm_image(vm_entry.uuid)
+ if r_status:
etcd_client.client.delete(vm_entry.key)
@@ -301,15 +247,16 @@ def transfer(request_event):
_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(env_vars.get('VM_PREFIX'), _uuid))
+ vm = get_vm(running_vms, join_path(env_vars.get('VM_PREFIX'), _uuid))
if vm:
tunnel = sshtunnel.SSHTunnelForwarder(
- (_host, 22),
+ _host,
ssh_username=env_vars.get("ssh_username"),
ssh_pkey=env_vars.get("ssh_pkey"),
- ssh_private_key_password=env_vars.get("ssh_private_key_password"),
remote_bind_address=("127.0.0.1", _port),
+ ssh_proxy_enabled=True,
+ ssh_proxy=(_host, 22)
)
try:
tunnel.start()
@@ -317,7 +264,7 @@ def transfer(request_event):
logger.exception("Couldn't establish connection to (%s, 22)", _host)
else:
vm.handle.command(
- "migrate", uri="tcp:{}:{}".format(_host, tunnel.local_bind_port)
+ "migrate", uri="tcp:0.0.0.0:{}".format(tunnel.local_bind_port)
)
status = vm.handle.command("query-migrate")["status"]
@@ -340,38 +287,22 @@ def transfer(request_event):
tunnel.close()
-def init_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.
- logger.info("%s Already running", _vm.key)
- return
-
- launch_vm(vm_entry, migration=True, migration_port=4444,
- destination_host_key=destination_host_key)
-
-
def launch_vm(vm_entry, migration=False, migration_port=None, destination_host_key=None):
logger.info("Starting %s", vm_entry.key)
vm = create_vm_object(vm_entry, migration=migration, migration_port=migration_port)
try:
vm.handle.launch()
- except Exception as e:
- logger.exception(e)
+ except Exception:
+ logger.exception("Error Occured while starting VM")
+ vm.handle.shutdown()
if migration:
# We don't care whether MachineError or any other error occurred
- vm.handle.shutdown()
+ pass
else:
# Error during typical launch of a vm
- vm_entry.add_log("Error Occurred while starting VM")
+ vm.handle.shutdown()
vm_entry.declare_killed()
vm_pool.put(vm_entry)
else:
@@ -383,7 +314,7 @@ def launch_vm(vm_entry, migration=False, migration_port=None, destination_host_k
r = RequestEntry.from_scratch(
type=RequestType.TransferVM,
hostname=vm_entry.hostname,
- parameters={"host": get_ipv4_address(), "port": 4444},
+ parameters={"host": get_ipv6_address(), "port": migration_port},
uuid=vm_entry.uuid,
destination_host_key=destination_host_key,
request_prefix=env_vars.get("REQUEST_PREFIX")
diff --git a/imagescanner/main.py b/imagescanner/main.py
index 97da589..4b41642 100755
--- a/imagescanner/main.py
+++ b/imagescanner/main.py
@@ -1,9 +1,9 @@
import json
import os
import subprocess
-import sys
-from config import etcd_client, env_vars
+from os.path import join as join_path
+from config import etcd_client, env_vars, image_storage_handler
from imagescanner import logger
@@ -20,20 +20,6 @@ def qemu_img_type(path):
def main():
- # If you are using env_vars.get('WITHOUT_CEPH') FLAG in .env
- # then please make sure that env_vars.get('IMAGE_DIR') directory
- # exists otherwise this script would fail
- if env_vars.get('WITHOUT_CEPH') and not os.path.isdir(env_vars.get('IMAGE_DIR')):
- print("You have set env_vars.get('WITHOUT_CEPH') to True. So,"
- "the {} must exists. But, it don't".format(env_vars.get('IMAGE_DIR')))
- sys.exit(1)
-
- try:
- subprocess.check_output(['which', 'qemu-img'])
- except Exception:
- print("qemu-img missing")
- sys.exit(1)
-
# We want to get images entries that requests images to be created
images = etcd_client.get_prefix(env_vars.get('IMAGE_PREFIX'), value_in_json=True)
images_to_be_created = list(filter(lambda im: im.value['status'] == 'TO_BE_CREATED', images))
@@ -44,7 +30,7 @@ def main():
image_owner = image.value['owner']
image_filename = image.value['filename']
image_store_name = image.value['store_name']
- image_full_path = os.path.join(env_vars.get('BASE_DIR'), image_owner, image_filename)
+ image_full_path = join_path(env_vars.get('BASE_DIR'), image_owner, image_filename)
image_stores = etcd_client.get_prefix(env_vars.get('IMAGE_STORE_PREFIX'), value_in_json=True)
user_image_store = next(filter(
@@ -58,43 +44,25 @@ def main():
logger.exception(e)
else:
# At least our basic data is available
-
qemu_img_convert_command = ["qemu-img", "convert", "-f", "qcow2",
"-O", "raw", image_full_path, "image.raw"]
- if env_vars.get('WITHOUT_CEPH'):
- image_import_command = ["mv", "image.raw", os.path.join(env_vars.get('IMAGE_DIR'), image_uuid)]
- snapshot_creation_command = ["true"]
- snapshot_protect_command = ["true"]
- else:
- image_import_command = ["rbd", "import", "image.raw",
- "{}/{}".format(image_store_pool, image_uuid)]
- snapshot_creation_command = ["rbd", "snap", "create",
- "{}/{}@protected".format(image_store_pool, image_uuid)]
- snapshot_protect_command = ["rbd", "snap", "protect",
- "{}/{}@protected".format(image_store_pool, image_uuid)]
-
- # First check whether the image is qcow2
-
if qemu_img_type(image_full_path) == "qcow2":
try:
# Convert .qcow2 to .raw
subprocess.check_output(qemu_img_convert_command)
-
- # Import image either to ceph/filesystem
- subprocess.check_output(image_import_command)
-
- # Create and Protect Snapshot
- subprocess.check_output(snapshot_creation_command)
- subprocess.check_output(snapshot_protect_command)
-
except Exception as e:
logger.exception(e)
-
else:
- # Everything is successfully done
- image.value["status"] = "CREATED"
- etcd_client.put(image.key, json.dumps(image.value))
+ # Import and Protect
+ r_status = image_storage_handler.import_image(src="image.raw",
+ dest=image_uuid,
+ protect=True)
+ if r_status:
+ # Everything is successfully done
+ image.value["status"] = "CREATED"
+ etcd_client.put(image.key, json.dumps(image.value))
+
else:
# The user provided image is either not found or of invalid format
image.value["status"] = "INVALID_IMAGE"
diff --git a/sanity_checks.py b/sanity_checks.py
new file mode 100644
index 0000000..2c645a5
--- /dev/null
+++ b/sanity_checks.py
@@ -0,0 +1,33 @@
+import sys
+import subprocess as sp
+
+from os.path import isdir
+from config import env_vars
+
+
+def check():
+ #########################
+ # ucloud-image-scanner #
+ #########################
+ if env_vars.get('STORAGE_BACKEND') == 'filesystem' and not isdir(env_vars.get('IMAGE_DIR')):
+ print("You have set STORAGE_BACKEND to filesystem. So,"
+ "the {} must exists. But, it don't".format(env_vars.get('IMAGE_DIR')))
+ sys.exit(1)
+
+ try:
+ sp.check_output(['which', 'qemu-img'])
+ except Exception:
+ print("qemu-img missing")
+ sys.exit(1)
+
+ ###############
+ # ucloud-host #
+ ###############
+
+ if env_vars.get('STORAGE_BACKEND') == 'filesystem' and not isdir(env_vars.get('VM_DIR')):
+ print("You have set STORAGE_BACKEND to filesystem. So, the vm directory mentioned"
+ " in .env file must exists. But, it don't.")
+ sys.exit(1)
+
+if __name__ == "__main__":
+ check()
\ No newline at end of file
diff --git a/scheduler/helper.py b/scheduler/helper.py
index 81b5869..79bfd70 100755
--- a/scheduler/helper.py
+++ b/scheduler/helper.py
@@ -23,16 +23,16 @@ def remaining_resources(host_specs, vms_specs):
for component in _vms_specs:
if isinstance(_vms_specs[component], str):
- _vms_specs[component] = int(bitmath.parse_string(_vms_specs[component]).to_MB())
+ _vms_specs[component] = int(bitmath.parse_string_unsafe(_vms_specs[component]).to_MB())
elif isinstance(_vms_specs[component], list):
- _vms_specs[component] = map(lambda x: int(bitmath.parse_string(x).to_MB()), _vms_specs[component])
+ _vms_specs[component] = map(lambda x: int(bitmath.parse_string_unsafe(x).to_MB()), _vms_specs[component])
_vms_specs[component] = reduce(lambda x, y: x + y, _vms_specs[component], 0)
for component in _remaining:
if isinstance(_remaining[component], str):
- _remaining[component] = int(bitmath.parse_string(_remaining[component]).to_MB())
+ _remaining[component] = int(bitmath.parse_string_unsafe(_remaining[component]).to_MB())
elif isinstance(_remaining[component], list):
- _remaining[component] = map(lambda x: int(bitmath.parse_string(x).to_MB()), _remaining[component])
+ _remaining[component] = map(lambda x: int(bitmath.parse_string_unsafe(x).to_MB()), _remaining[component])
_remaining[component] = reduce(lambda x, y: x + y, _remaining[component], 0)
_remaining.subtract(_vms_specs)
diff --git a/scheduler/main.py b/scheduler/main.py
index 507ac44..1d8dc44 100755
--- a/scheduler/main.py
+++ b/scheduler/main.py
@@ -23,8 +23,6 @@ def main():
]:
for request_event in request_iterator:
request_entry = RequestEntry(request_event)
- logger.debug("%s, %s", request_entry.key, request_entry.value)
-
# Never Run time critical mechanism inside timeout
# mechanism because timeout mechanism only comes
# when no other event is happening. It means under
@@ -33,10 +31,10 @@ def main():
# Detect hosts that are dead and set their status
# to "DEAD", and their VMs' status to "KILLED"
- logger.debug("TIMEOUT event occured")
dead_hosts = dead_host_detection()
- logger.debug("Dead hosts: %s", dead_hosts)
- dead_host_mitigation(dead_hosts)
+ if dead_hosts:
+ logger.debug("Dead hosts: %s", dead_hosts)
+ dead_host_mitigation(dead_hosts)
# If there are VMs that weren't assigned a host
# because there wasn't a host available which
@@ -52,6 +50,8 @@ def main():
request_pool.put(r)
elif request_entry.type == RequestType.ScheduleVM:
+ logger.debug("%s, %s", request_entry.key, request_entry.value)
+
vm_entry = vm_pool.get(request_entry.uuid)
if vm_entry is None:
logger.info("Trying to act on {} but it is deleted".format(request_entry.uuid))
@@ -67,7 +67,7 @@ def main():
hosts=[host_pool.get(request_entry.destination)])
except NoSuitableHostFound:
logger.info("Requested destination host doesn't have enough capacity"
- "to hold %s", vm_entry.uuid)
+ "to hold %s" % vm_entry.uuid)
else:
r = RequestEntry.from_scratch(type=RequestType.InitVMMigration,
uuid=request_entry.uuid,
diff --git a/ucloud.py b/ucloud.py
index 8774fa3..28979b3 100644
--- a/ucloud.py
+++ b/ucloud.py
@@ -3,6 +3,7 @@ import multiprocessing as mp
import logging
from os.path import join as join_path
+from sanity_checks import check
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser(prog='ucloud',
@@ -21,30 +22,36 @@ if __name__ == "__main__":
format="%(name)s %(asctime)s: %(levelname)s - %(message)s",
datefmt="%d-%b-%y %H:%M:%S",
)
+ try:
+ check()
- if args.component == 'api':
- from api.main import main
+ if args.component == 'api':
+ from api.main import main
- main()
- elif args.component == 'host':
- from host.main import main
+ main()
+ elif args.component == 'host':
+ from host.main import main
- hostname = args.component_args
- mp.set_start_method('spawn')
- main(*hostname)
- elif args.component == 'scheduler':
- from scheduler.main import main
+ hostname = args.component_args
+ mp.set_start_method('spawn')
+ main(*hostname)
+ elif args.component == 'scheduler':
+ from scheduler.main import main
- main()
- elif args.component == 'filescanner':
- from filescanner.main import main
+ main()
+ elif args.component == 'filescanner':
+ from filescanner.main import main
- main()
- elif args.component == 'imagescanner':
- from imagescanner.main import main
+ main()
+ elif args.component == 'imagescanner':
+ from imagescanner.main import main
- main()
- elif args.component == 'metadata':
- from metadata.main import main
+ main()
+ elif args.component == 'metadata':
+ from metadata.main import main
- main()
+ main()
+
+ except Exception as e:
+ logging.exception(e)
+ print(e)
\ No newline at end of file