├── .gitignore ├── .travis.yml ├── CI └── before_script.sh ├── Documentation └── Cunik.md ├── LICENSE ├── README.md ├── api ├── __init__.py ├── app.py ├── config │ ├── __init__.py │ ├── base_config.py │ ├── dev_config.py │ └── test_config.py ├── models │ ├── __init__.py │ ├── cunik │ │ ├── __init__.py │ │ └── cunik.py │ ├── cunik_registry │ │ ├── __init__.py │ │ └── cunik_registry.py │ ├── image │ │ ├── __init__.py │ │ └── image.py │ ├── image_registry │ │ ├── __init__.py │ │ └── image_registry.py │ └── nic_pool │ │ ├── __init__.py │ │ └── nic_pool.py ├── router │ ├── __init__.py │ ├── cunik │ │ ├── __init__.py │ │ └── cunik.py │ ├── image │ │ └── image.py │ ├── route │ │ ├── __init__.py │ │ └── route.py │ ├── router.py │ └── system │ │ └── system.py └── utils │ ├── __init__.py │ └── utils.py ├── backend ├── __init__.py ├── unikernel │ ├── __init__.py │ ├── osv │ │ ├── __init__.py │ │ ├── imgedit.py │ │ └── nbd_client.py │ ├── rumprun │ │ └── __init__.py │ └── utils.py └── vm │ ├── __init__.py │ └── vm.py ├── dev ├── start_cunik.py ├── start_vm.sh ├── test_nginx.py ├── test_redis.py ├── test_scalability.py ├── test_vm.py └── utils │ ├── gen_conf.py │ ├── gen_metainfo.py │ └── gen_paras.py ├── engine.py ├── requirements.txt └── tests ├── __init__.py ├── test_cunik_registry └── __init__.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .flaskenv 4 | *.pyc 5 | *.pyo 6 | env/ 7 | env* 8 | dist/ 9 | build/ 10 | *.egg 11 | *.egg-info/ 12 | _mailinglist 13 | .tox/ 14 | .cache/ 15 | .pytest_cache/ 16 | .idea/ 17 | docs/_build/ 18 | 19 | # Coverage reports 20 | htmlcov/ 21 | .coverage 22 | .coverage.* 23 | *,cover 24 | 25 | # Created by https://www.gitignore.io/api/code,macos,pycharm 26 | 27 | ### Code ### 28 | # Visual Studio Code - https://code.visualstudio.com/ 29 | .settings/ 30 | .vscode/ 31 | tsconfig.json 32 | jsconfig.json 33 | 34 | ### macOS ### 35 | *.DS_Store 36 | .AppleDouble 37 | .LSOverride 38 | 39 | # Icon must end with two \r 40 | Icon 41 | 42 | # Thumbnails 43 | ._* 44 | 45 | # Files that might appear in the root of a volume 46 | .DocumentRevisions-V100 47 | .fseventsd 48 | .Spotlight-V100 49 | .TemporaryItems 50 | .Trashes 51 | .VolumeIcon.icns 52 | .com.apple.timemachine.donotpresent 53 | 54 | # Directories potentially created on remote AFP share 55 | .AppleDB 56 | .AppleDesktop 57 | Network Trash Folder 58 | Temporary Items 59 | .apdisk 60 | 61 | ### PyCharm ### 62 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 63 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 64 | 65 | # User-specific stuff: 66 | .idea/**/workspace.xml 67 | .idea/**/tasks.xml 68 | .idea/dictionaries 69 | 70 | # Sensitive or high-churn files: 71 | .idea/**/dataSources/ 72 | .idea/**/dataSources.ids 73 | .idea/**/dataSources.xml 74 | .idea/**/dataSources.local.xml 75 | .idea/**/sqlDataSources.xml 76 | .idea/**/dynamic.xml 77 | .idea/**/uiDesigner.xml 78 | 79 | # Gradle: 80 | .idea/**/gradle.xml 81 | .idea/**/libraries 82 | 83 | # CMake 84 | cmake-build-debug/ 85 | 86 | # Mongo Explorer plugin: 87 | .idea/**/mongoSettings.xml 88 | 89 | ## File-based project format: 90 | *.iws 91 | 92 | ## Plugin-specific files: 93 | 94 | # IntelliJ 95 | /out/ 96 | 97 | # mpeltonen/sbt-idea plugin 98 | .idea_modules/ 99 | 100 | # JIRA plugin 101 | atlassian-ide-plugin.xml 102 | 103 | # Cursive Clojure plugin 104 | .idea/replstate.xml 105 | 106 | # Ruby plugin and RubyMine 107 | /.rakeTasks 108 | 109 | # Crashlytics plugin (for Android Studio and IntelliJ) 110 | com_crashlytics_export_strings.xml 111 | crashlytics.properties 112 | crashlytics-build.properties 113 | fabric.properties 114 | 115 | ### PyCharm Patch ### 116 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 117 | 118 | # *.iml 119 | # modules.xml 120 | # .idea/misc.xml 121 | # *.ipr 122 | 123 | # Sonarlint plugin 124 | .idea/sonarlint 125 | 126 | cunik.log 127 | config.py 128 | venv 129 | 130 | # End of https://www.gitignore.io/api/code,macos,pycharm 131 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 3.6 5 | 6 | before_install: 7 | - sudo apt-get install -qq libvirt-dev 8 | 9 | install: 10 | - pip install -r requirements.txt 11 | 12 | before_script: 13 | - chmod +x ./CI/before_script.sh && ./CI/before_script.sh 14 | 15 | script: sudo env "PATH=$PATH" python engine.py test 16 | -------------------------------------------------------------------------------- /CI/before_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo mkdir /var/cunik 4 | sudo mkdir /var/cunik/images 5 | -------------------------------------------------------------------------------- /Documentation/Cunik.md: -------------------------------------------------------------------------------- 1 | ## Why does Cunik exist? 2 | 3 | Containers are a good isolation mechanism, but not good enough. 4 | 5 | ### Safety 6 | 7 | As for containers, once an application has any security problems, it will affect all the applications which are running on the same operating system. Luckily, unikernel can solve the problem better. 8 | 9 | ### Efficiency 10 | 11 | The success of Docker increases the availability of system resources greatly. However, since Docker generally runs on a streamlined Linux kernel system, its speed is limited by the Linux kernel. Otherwise, unikernel can meet the needs of customized cores and it can reduce performance overhead. 12 | 13 | Unikernel is a good candidate for improving efficiency and safety. However, it is hard to build and manage, since there are so many different implementations and each of them uses different toolchains when building and deploying. 14 | 15 | So we present Cunik, which aims at enabling you to get Unikernel images and deploy them in several commands, and it is easy to configure just like writing Dockerfiles. 16 | 17 | ## What can you do with it? 18 | 19 | By using Cunik, you can: 20 | 21 | - get better isolation and higher performance than using container techs; 22 | - easily build and manage unikernel applications than directly playing with unikernel implementations like Rumprun and ClickOS. 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cunik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cunik 2 | ***(The project is under development.)*** 3 | 4 | [![Build Status](https://travis-ci.org/Cunik/Cunik-engine.svg?branch=master)](https://travis-ci.org/Cunik/Cunik-engine) 5 | 6 | Cunik is a solution for easily building, packaging, delivering, fetching, deploying and managing unikernel images over different unikernel implementations like Rumprun, OSv, MirageOS and IncludeOS, which enables people launching unikernel applications by several command lines. 7 | 8 | Read more about Cunik [here](./Documentation/Cunik.md). 9 | 10 | ## Getting started 11 | 12 | These instructions will get you running Cunik Engine and a Cunik-nginx on your machine. 13 | 14 | ### Get Source Code 15 | 16 | ```shell 17 | git clone https://github.com/Cunik/Cunik-engine.git 18 | ``` 19 | 20 | ### Install System Package Dependencies 21 | 22 | On Debian, install the following using apt: 23 | 24 | * libvirt-daemon-system 25 | * libvirt-dev 26 | 27 | ### Install Python Package Dependencies 28 | 29 | ```shell 30 | pip3 install -r requirements.txt 31 | ``` 32 | 33 | ### Create Cunik-Root Manually 34 | 35 | Cunik intends to load its images and other information from a single folder(`/var/cunik`). For now, you need to create it manually: get [this](https://www.dropbox.com/s/fgrs238vfp111pn/Cunik-root.tar.gz?dl=0), decompress it, and copy or link to `/var/cunik`. 36 | 37 | ### Start Cunik-engine 38 | 39 | A Cunik Engine is a daemon that listens on the host and waits for requests from clients. Now, let's launch the Cunik Engine. 40 | 41 | Run the daemon: 42 | 43 | ```shell 44 | python3 engine.py runserver 45 | ``` 46 | 47 | Actually, we need to be `root` to configure network and manage vm. So run the command line above as `root` or use `sudo`, and use what ever techniques to prevent unexpected damage. 48 | 49 | ### Start a Cunik and Test It 50 | 51 | Use [Cunik-cli](https://github.com/Cunik/Cunik-cli) to start a Cunik. Example: 52 | 53 | ``` 54 | cunik-cli create nginx 10.0.125.3 55 | ``` 56 | 57 | **Don't** change **`10.0.125`** since it is hard-coded for now. 58 | 59 | Then `curl 10.0.125.3` to see if it works. 60 | 61 | Following are the images that you can give it a try: 62 | 63 | * nginx 64 | 65 | Runmrun implementation of nginx. 66 | 67 | * redis-server 68 | 69 | Rumprun implementation of redis. Test it with `redis-cli -h
`. 70 | 71 | * redis-server-osv 72 | 73 | OSv implementation of reids. Unable to access for now(we are sure it's online(`nmap`-ed it)). We'd be glad if you can help us fix it. 74 | 75 | ## Contributing 76 | 77 | (none) 78 | 79 | ## Links 80 | 81 | (none) 82 | 83 | ## Licensing 84 | 85 | The code in this project is licensed under MIT license. 86 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | __all__ = ['create_app'] 4 | 5 | from .app import create_app 6 | -------------------------------------------------------------------------------- /api/app.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | from flask import Flask 4 | from .config import configs 5 | 6 | 7 | def create_app(config_name='default'): 8 | app = Flask(__name__) 9 | config_obj = configs.get(config_name) 10 | app.config.from_object(config_obj) 11 | config_obj.init_app(app) 12 | 13 | # register bps 14 | from .router import routes 15 | 16 | for r in routes: 17 | app.register_blueprint(r.bp, url_prefix=r.prefix) 18 | 19 | app.debug = False # 20 | 21 | return app 22 | -------------------------------------------------------------------------------- /api/config/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | __all__ = ['configs', 'default_config'] 4 | 5 | from .dev_config import DevConfig 6 | from .test_config import TestConfig 7 | from .base_config import BaseConfig 8 | 9 | configs = { 10 | 'default': DevConfig, 11 | 'dev': DevConfig, 12 | 'test': TestConfig 13 | } 14 | 15 | default_config = configs['default'] -------------------------------------------------------------------------------- /api/config/base_config.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | import logging 3 | import os 4 | from logging.handlers import RotatingFileHandler 5 | 6 | 7 | class BaseConfig: 8 | CUNIK_ROOT = os.path.realpath('/var/cunik') 9 | LOG_FILE = os.path.realpath('/var/log/cunik.log') 10 | REGISTRY_ROOT = os.path.join(CUNIK_ROOT, 'registry') 11 | BRIDGE_NAME = 'cunik' 12 | SUBNET = '10.0.125.1/24' 13 | 14 | @staticmethod 15 | def init_app(app): 16 | pass 17 | -------------------------------------------------------------------------------- /api/config/dev_config.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | from .base_config import BaseConfig 4 | 5 | 6 | class DevConfig(BaseConfig): 7 | DEBUG = True 8 | -------------------------------------------------------------------------------- /api/config/test_config.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | from .base_config import BaseConfig 4 | 5 | 6 | class TestConfig(BaseConfig): 7 | DEBUG = True 8 | -------------------------------------------------------------------------------- /api/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .cunik_registry import cunik_registry 2 | from .image_registry import image_registry 3 | from .nic_pool import nic_pool 4 | -------------------------------------------------------------------------------- /api/models/cunik/__init__.py: -------------------------------------------------------------------------------- 1 | from .cunik import Cunik, CunikConfig 2 | -------------------------------------------------------------------------------- /api/models/cunik/cunik.py: -------------------------------------------------------------------------------- 1 | from backend.vm import VM 2 | from os import path 3 | import json 4 | import sys 5 | import os 6 | import uuid 7 | import backend.unikernel as uk 8 | 9 | 10 | class CunikConfig: 11 | """Config of a cunik, constructed when the user wants to create a Cunik. 12 | 13 | Specifies: 14 | name * 15 | image * 16 | port * 17 | cmdline 18 | """ 19 | def __init__(self, name, ipv4_addr, cmdline): 20 | self.name = name 21 | self.ipv4_addr = ipv4_addr 22 | self.cmdline = cmdline 23 | 24 | 25 | class Cunik: 26 | """Represent a cunik, consists of image, vm and config. 27 | 28 | Usage: 29 | >>> cu = Cunik(...) # Now there is a new cunik in cunik registry along with the vm instance 30 | >>> cu.start() # Now it starts, and the new status is updated in cunik registry 31 | >>> cu.stop() 32 | >>> del cu # NOTICE: This really destroys corresponding vm and remove this cunik from registry 33 | """ 34 | def __init__(self, image, config: CunikConfig): 35 | """Initialize the cunik.""" 36 | self.image = image 37 | self.config = config 38 | 39 | # Allocate resources, for now, only nic 40 | from ..nic_pool import nic_pool 41 | self.nic_name = nic_pool.alloc() 42 | 43 | vmc = uk.configure(image, config, nic_name=self.nic_name) 44 | # Then construct vm 45 | self.vm = VM(vmc) 46 | 47 | @property 48 | def uuid(self): 49 | return uuid.UUID(self.vm.uuid) 50 | 51 | @property 52 | def name(self): 53 | return self.vm.domain.name() 54 | 55 | @property 56 | def status(self): 57 | return self.vm.domain.state() 58 | 59 | def start(self): 60 | """Start the cunik.""" 61 | # Start the vm 62 | self.vm.start() 63 | 64 | def stop(self): 65 | """Stop the cunik.""" 66 | # Stop the vm 67 | self.vm.stop() 68 | 69 | def destroy(self): 70 | """Destroy a cunik according to the config.""" 71 | # Destroy the vm 72 | self.vm.destroy() 73 | 74 | def to_json(self): 75 | return {'vm': self.vm.to_json()} 76 | 77 | 78 | class CunikApi: 79 | pass -------------------------------------------------------------------------------- /api/models/cunik_registry/__init__.py: -------------------------------------------------------------------------------- 1 | from .cunik_registry import CunikRegistry 2 | from api.config import default_config 3 | import atexit 4 | import os 5 | 6 | 7 | cunik_registry = CunikRegistry(os.path.join(default_config.CUNIK_ROOT, 'cuniks')) 8 | 9 | 10 | def exit_handler(): 11 | cunik_registry.clear() 12 | 13 | 14 | atexit.register(exit_handler) 15 | -------------------------------------------------------------------------------- /api/models/cunik_registry/cunik_registry.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | import json 3 | import time 4 | import uuid 5 | import shutil 6 | from api.models.cunik import Cunik, CunikConfig 7 | 8 | 9 | class CunikRegistry: 10 | """Local Cunik registry.""" 11 | def __init__(self, root): 12 | self.root = root 13 | self._cuniks = dict() 14 | 15 | def create(self, name, image_name, ipv4_addr, cmdline=None): 16 | cfg = CunikConfig(name=name, ipv4_addr=ipv4_addr, cmdline=cmdline) 17 | 18 | from ..image_registry import image_registry 19 | image = image_registry.get_image(image_name) # TODO: no error handling for now 20 | 21 | cu = Cunik(image, cfg) 22 | self.register(cu) 23 | cu.start() 24 | 25 | @staticmethod 26 | def convert_from_json(s: str): 27 | d = {} 28 | for k, v in json.loads(s).items(): 29 | w = Cunik.from_json(v) 30 | if w is not None: 31 | d[uuid.UUID(k)] = w 32 | return d 33 | 34 | def convert_to_json(self): 35 | d = {} 36 | for k, v in self._cuniks.items(): 37 | w = v.to_json() 38 | if w is not None: 39 | d[str(k)] = w 40 | return d 41 | 42 | def commit(self): 43 | pass 44 | 45 | def register(self, cunik: Cunik): 46 | assert not self.query(cunik.uuid) 47 | cunik.create_time = time.time() 48 | self._cuniks[cunik.uuid] = cunik 49 | self.commit() 50 | 51 | def remove(self, cunik): 52 | assert self.query(cunik.uuid) 53 | self._cuniks.pop(cunik.uuid) 54 | self.commit() 55 | 56 | def populate(self, cunik): 57 | assert self.query(cunik.uuid) 58 | self._cuniks[cunik.uuid] = cunik 59 | self.commit() 60 | 61 | def query(self, cid: uuid.UUID): 62 | if isinstance(cid, str): 63 | cid = uuid.UUID(cid) 64 | return self._cuniks.get(cid) 65 | 66 | def get_id_list(self): 67 | return list(self._cuniks.keys()) 68 | 69 | def clear(self): 70 | for cu in self._cuniks.values(): 71 | cu.destroy() 72 | self._cuniks.clear() 73 | try: 74 | shutil.rmtree(self.root) 75 | except FileNotFoundError: 76 | pass 77 | -------------------------------------------------------------------------------- /api/models/image/__init__.py: -------------------------------------------------------------------------------- 1 | from .image import Image 2 | -------------------------------------------------------------------------------- /api/models/image/image.py: -------------------------------------------------------------------------------- 1 | """class Image.""" 2 | 3 | 4 | import json 5 | from os import path 6 | 7 | 8 | class Image: 9 | def __init__(self, root): 10 | self.root = root 11 | 12 | # Load json and fill meta information 13 | with open(path.join(self.root, 'metadata.json')) as f: 14 | metadata = json.load(f) 15 | 16 | # Fill main information 17 | self.name = metadata['name'] 18 | self.unikernel = metadata['unikernel']['name'] 19 | self.default_cmdline = metadata['default_cmdline'] 20 | -------------------------------------------------------------------------------- /api/models/image_registry/__init__.py: -------------------------------------------------------------------------------- 1 | from .image_registry import ImageRegistry 2 | from api.config import default_config 3 | import os 4 | 5 | image_registry = ImageRegistry(os.path.join(default_config.CUNIK_ROOT, 'images')) 6 | -------------------------------------------------------------------------------- /api/models/image_registry/image_registry.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from ..image import Image 3 | 4 | 5 | class ImageRegistry: 6 | """The registry of images. 7 | 8 | Usage: 9 | >>> ir = ImageRegistry() 10 | >>> image = ir.get_image('nginx') 11 | """ 12 | def __init__(self, root): 13 | self.root = root 14 | 15 | def get_image(self, name: str): 16 | return Image(root=path.join(self.root, name)) 17 | -------------------------------------------------------------------------------- /api/models/nic_pool/__init__.py: -------------------------------------------------------------------------------- 1 | from .nic_pool import NICPool 2 | from api.config import default_config 3 | import atexit 4 | 5 | nic_pool = NICPool(default_config.SUBNET) 6 | 7 | 8 | def exit_handler(): 9 | nic_pool.release() 10 | 11 | 12 | atexit.register(exit_handler) 13 | -------------------------------------------------------------------------------- /api/models/nic_pool/nic_pool.py: -------------------------------------------------------------------------------- 1 | import pyroute2 as pr 2 | from api.config import default_config 3 | 4 | 5 | class NICPool: 6 | """NICPool. 7 | 8 | Use pyroute2.IPDB to manage interfaces, and memorizes the objects created. 9 | """ 10 | def __init__(self, subnet): 11 | self.ipdb = pr.IPDB() 12 | self.bridge = self.ipdb.create( 13 | kind='bridge', 14 | ifname=default_config.BRIDGE_NAME 15 | ).add_ip(default_config.SUBNET).up().commit() 16 | self.nics = {} 17 | 18 | def alloc(self): 19 | # Pick a name and create the tap device 20 | nicname = 'cunik_tap{}'.format(len(self.nics)) 21 | nic = self.ipdb.create(kind='tuntap', mode='tap', ifname=nicname).commit() 22 | self.bridge.add_port(nic).commit() 23 | self.nics[nicname] = nic 24 | return nicname 25 | 26 | def free(self, nicname): 27 | # Destroy the tap device 28 | self.nics[nicname].down().remove().commit() 29 | self.nics.pop(nicname) 30 | 31 | def release(self): 32 | for n in self.nics.values(): 33 | n.down().remove() 34 | self.bridge.down().remove() 35 | self.ipdb.commit().release() 36 | -------------------------------------------------------------------------------- /api/router/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | __all__ = ['routes'] 4 | 5 | from .router import routes 6 | 7 | -------------------------------------------------------------------------------- /api/router/cunik/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | __all__ = ['cunik_bp'] 4 | 5 | from .cunik import bp as cunik_bp 6 | -------------------------------------------------------------------------------- /api/router/cunik/cunik.py: -------------------------------------------------------------------------------- 1 | """Take the parameters from router, parse them, execute and return the results.""" 2 | import json 3 | 4 | from flask import request, Blueprint 5 | from api.models import cunik_registry 6 | from random import randint 7 | 8 | bp = Blueprint('cunik', __name__) 9 | 10 | 11 | @bp.route('/create', methods=['POST']) 12 | def create(): 13 | image_name = request.form.get('image_name') 14 | params = {} 15 | params['ipv4_addr'] = request.form.get('ipv4_addr') 16 | print(image_name, params, '=======================') 17 | cunik_registry.create( 18 | name=image_name + str(randint(0, 1048576)), # Well, okay for now 19 | image_name=image_name, 20 | ipv4_addr=params['ipv4_addr'], 21 | cmdline=None 22 | ) 23 | 24 | 25 | @bp.route('/list', methods=['GET']) 26 | def list(): 27 | return json.dumps(CunikApi.list()) 28 | 29 | 30 | @bp.route('/info', methods=['POST']) 31 | def info(): 32 | cid = request.form.get('cid') 33 | return CunikApi.info(cid=cid) 34 | 35 | 36 | @bp.route('/stop', methods=['POST']) 37 | def stop(): 38 | cid = request.form.get('cid') 39 | return CunikApi.stop(cid=cid) 40 | 41 | 42 | @bp.route('/remove', methods=['POST']) 43 | def remove(): 44 | cid = request.form.get('cid') 45 | return CunikApi.remove(cid=cid) 46 | -------------------------------------------------------------------------------- /api/router/image/image.py: -------------------------------------------------------------------------------- 1 | """Take the parameters from router, parse them, execute and return the results.""" 2 | 3 | 4 | def list(): 5 | pass 6 | 7 | 8 | def inspect(): 9 | pass 10 | -------------------------------------------------------------------------------- /api/router/route/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | __all__ = ['Route'] 4 | 5 | from .route import Route 6 | -------------------------------------------------------------------------------- /api/router/route/route.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | class Route: 4 | 5 | def __init__(self, bp, prefix): 6 | self.bp = bp 7 | self.prefix = prefix 8 | -------------------------------------------------------------------------------- /api/router/router.py: -------------------------------------------------------------------------------- 1 | """Route. 2 | /run POST 3 | /info/ GET 4 | """ 5 | 6 | from .route import Route 7 | from .cunik import cunik_bp 8 | 9 | routes = [ 10 | Route(cunik_bp, '/cunik') 11 | ] 12 | -------------------------------------------------------------------------------- /api/router/system/system.py: -------------------------------------------------------------------------------- 1 | """Take the parameters from router, parse them, execute and return the results.""" 2 | -------------------------------------------------------------------------------- /api/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | from .utils import * -------------------------------------------------------------------------------- /api/utils/utils.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | import os 4 | 5 | 6 | def ensure_file(directory, filename, content=''): 7 | if not os.path.isdir(directory): 8 | os.makedirs(directory) 9 | if not os.path.exists(filename): 10 | open(os.path.join(directory, filename), 'w').close() 11 | if not os.path.getsize(os.path.join(directory, filename)): 12 | with open(os.path.join(directory, filename), 'w') as f: 13 | f.write(content) 14 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cunik/Cunik-engine/9559db6167e1e1a22e5906b28c57040883f4ff97/backend/__init__.py -------------------------------------------------------------------------------- /backend/unikernel/__init__.py: -------------------------------------------------------------------------------- 1 | from .rumprun import Rumprun 2 | from .osv import OSv 3 | 4 | 5 | unikernels = { 6 | 'rumprun': Rumprun, 7 | 'osv': OSv, 8 | } 9 | 10 | 11 | def configure(image, config, nic_name): 12 | """Construct a vm config from cunik config using different Unikernel backends.""" 13 | # TODO: We should have better way to do this 14 | return unikernels[image.unikernel].configure(image, config, nic_name) 15 | -------------------------------------------------------------------------------- /backend/unikernel/osv/__init__.py: -------------------------------------------------------------------------------- 1 | """Implements interface for OSv unikernels.""" 2 | 3 | 4 | from backend.vm import VMConfig 5 | from os import path 6 | from .imgedit import set_cmdline 7 | 8 | 9 | class OSv: 10 | cmdline_template = "--ip=eth0,{ipv4_addr},255.255.255.0 --nameserver=10.0.125.0 {extra_cmdline}" 11 | 12 | @staticmethod 13 | def configure(image, config, nic_name): 14 | cmdline = OSv.cmdline_template.format( 15 | ipv4_addr=config.ipv4_addr, 16 | extra_cmdline=config.cmdline if config.cmdline else image.default_cmdline, 17 | ) 18 | set_cmdline(path.join(image.root, 'system.qemu'), cmdline) 19 | vmc = VMConfig( 20 | name=config.name, 21 | nic_name=nic_name, 22 | num_cpus=4, 23 | vdisk_path=path.join(image.root, 'system.qemu'), 24 | vdisk_format='qcow2', 25 | memory_size=1024000 26 | ) 27 | return vmc 28 | -------------------------------------------------------------------------------- /backend/unikernel/osv/imgedit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys, struct 4 | import subprocess 5 | import time 6 | 7 | from .nbd_client import nbd_client 8 | from random import randint 9 | 10 | 11 | def set_cmdline(image_path, cmdline): 12 | _devnull = open('/dev/null', 'w') 13 | 14 | cmd = 'setargs' 15 | args = [image_path, cmdline] 16 | 17 | args_offset = 512 18 | 19 | def chs(x): 20 | sec_per_track = 63 21 | heads = 255 22 | 23 | c = (x // sec_per_track) // heads 24 | h = (x // sec_per_track) % heads 25 | s = x % sec_per_track + 1 26 | 27 | # see http://en.wikipedia.org/wiki/Master_Boot_Record 28 | if c > 1023: 29 | c = 1023 30 | h = 254 31 | s = 63 32 | 33 | return c, h, s 34 | 35 | def read_chars_up_to_null(file): 36 | while True: 37 | try: 38 | c = file.read(1) 39 | if c == '\0': 40 | raise StopIteration 41 | yield c 42 | except ValueError: 43 | raise StopIteration 44 | 45 | def read_cstr(file): 46 | return ''.join(read_chars_up_to_null(file)) 47 | 48 | def write_cstr(file, str): 49 | file.write(str.encode()) 50 | file.write(b'\0') 51 | 52 | class nbd_file(object): 53 | 54 | def __init__(self, filename): 55 | self._filename = filename 56 | self._offset = 0 57 | self._buf = None 58 | self._closed = True 59 | nbd_port = randint(10809, 20809) 60 | self._process = subprocess.Popen("qemu-nbd -p %d %s" % (nbd_port, filename), 61 | shell=True, stdout=_devnull) 62 | # wait for qemu-nbd to start: this thing doesn't tell anything on stdout 63 | while True: 64 | try: 65 | self._client = nbd_client("localhost", nbd_port) 66 | break 67 | except: 68 | if self._process.poll() != None: 69 | raise Exception('Qemu terminated with exit code %d' % self._process.returncode) 70 | time.sleep(0.1) 71 | self._closed = False 72 | 73 | def __del__(self): 74 | self.close() 75 | 76 | def __enter__(self): 77 | return self 78 | 79 | def __exit__(self, type, value, traceback): 80 | self.close() 81 | 82 | def close(self): 83 | if self._closed: 84 | return 85 | # send disconnect to nbd server 86 | self._client.close() 87 | # wait for server to exit 88 | if self._process.wait(): 89 | raise Exception('Qemu terminated with exit code %d' % self._process.returncode) 90 | self._closed = True 91 | 92 | def seek(self, offset): 93 | self._offset = offset 94 | 95 | def _sect_begin(self, offset): 96 | return (offset // 512) * 512 97 | 98 | def _offset_in_sect(self, offset): 99 | return offset % 512 100 | 101 | def _sect_size(self, offset, count): 102 | size = self._offset_in_sect(offset) + count 103 | return ((size // 512) + 1) * 512 104 | 105 | def read(self, count): 106 | sect_begin = self._sect_begin(self._offset) 107 | offset_in_sect = self._offset_in_sect(self._offset) 108 | sect_size = self._sect_size(self._offset, count) 109 | 110 | self._buf = self._client.read(sect_begin, sect_size) 111 | 112 | data = self._buf[offset_in_sect: offset_in_sect + count] 113 | 114 | self._offset += count 115 | 116 | return data 117 | 118 | def write(self, data): 119 | count = len(data) 120 | sect_begin = self._sect_begin(self._offset) 121 | offset_in_sect = self._offset_in_sect(self._offset) 122 | sect_size = self._sect_size(self._offset, count) 123 | 124 | self._buf = self._client.read(sect_begin, sect_size) 125 | 126 | buf = self._buf[0: offset_in_sect] + data + \ 127 | self._buf[offset_in_sect + count:] 128 | 129 | self._client.write(buf, sect_begin) 130 | self._client.flush() 131 | 132 | self._offset += count 133 | 134 | return count 135 | 136 | def size(self): 137 | self._client.size() 138 | 139 | if cmd == 'setargs': 140 | img = args[0] 141 | args = args[1:] 142 | argstr = str.join(' ', args) 143 | with nbd_file(img) as f: 144 | f.seek(args_offset) 145 | write_cstr(f, argstr) 146 | elif cmd == 'getargs': 147 | img = args[0] 148 | with nbd_file(img) as f: 149 | f.seek(args_offset) 150 | print(read_cstr(f)) 151 | elif cmd == 'setsize': 152 | img = args[0] 153 | size = int(args[1]) 154 | block_size = 32 * 1024 155 | blocks = (size + block_size - 1) // block_size 156 | f = nbd_file(img) 157 | f.seek(0x10) 158 | f.write(struct.pack('H', blocks)) 159 | f.close() 160 | elif cmd == 'setpartition': 161 | img = args[0] 162 | partition = int(args[1]) 163 | start = int(args[2]) 164 | size = int(args[3]) 165 | partition = 0x1be + ((partition - 1) * 0x10) 166 | f = nbd_file(img) 167 | 168 | fsize = f.size() 169 | 170 | cyl, head, sec = chs(start // 512) 171 | cyl_end, head_end, sec_end = chs((start + size) // 512) 172 | 173 | f.seek(partition + 1) 174 | f.write(struct.pack('B', head)) 175 | f.seek(partition + 5) 176 | f.write(struct.pack('B', head_end)) 177 | 178 | f.seek(partition + 2) 179 | f.write(struct.pack('H', (cyl << 6) | sec)) 180 | f.seek(partition + 6) 181 | f.write(struct.pack('H', (cyl_end << 6) | sec_end)) 182 | 183 | system_id = 0x83 184 | f.seek(partition + 4) 185 | f.write(struct.pack('B', system_id)) 186 | 187 | f.seek(partition + 8) 188 | f.write(struct.pack('I', start // 512)) 189 | f.seek(partition + 12) 190 | f.write(struct.pack('I', size // 512)) 191 | f.close() 192 | -------------------------------------------------------------------------------- /backend/unikernel/osv/nbd_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2013 Nodalink, SARL. 5 | # 6 | # Simple nbd client used to connect to qemu-nbd 7 | # 8 | # author: Benoît Canet 9 | # 10 | # This work is open source software, licensed under the terms of the 11 | # BSD license as described in the LICENSE file in the top-level directory. 12 | # 13 | 14 | import socket 15 | import struct 16 | 17 | class nbd_client(object): 18 | 19 | READ = 0 20 | WRITE = 1 21 | DISCONNECT = 2 22 | FLUSH = 3 23 | 24 | FLAG_HAS_FLAGS = (1 << 0) 25 | FLAG_SEND_FLUSH = (1 << 2) 26 | 27 | def __init__(self, hostname, port=10809): 28 | self._flushed = True 29 | self._closed = True 30 | self._is_read = False 31 | self._handle = 0 32 | self._length = 0 33 | self._s = socket.create_connection((hostname, port)) 34 | self._closed = False 35 | self._old_style_handshake() 36 | 37 | def __del__(self): 38 | self.close() 39 | 40 | def close(self): 41 | if not self._flushed: 42 | self.flush() 43 | if not self._closed: 44 | self._disconnect() 45 | self._closed = True 46 | 47 | def _old_style_handshake(self): 48 | nbd_magic = self._s.recv(len("NBDMAGIC")) 49 | assert(nbd_magic == b'NBDMAGIC') 50 | buf = self._s.recv(8 + 8 + 4) 51 | (magic, self._size, self._flags) = struct.unpack(">QQL", buf) 52 | assert(magic == 0x00420281861253) 53 | # ignore trailing zeroes 54 | self._s.recv(124) 55 | 56 | def _build_header(self, request_type, offset, length): 57 | self._is_read = False 58 | header = struct.pack('>LLQQL', 0x25609513, 59 | request_type, self._handle, offset, length) 60 | return header 61 | 62 | def _parse_reply(self): 63 | data = "" 64 | reply = self._s.recv(4 + 4 + 8) 65 | (magic, errno, handle) = struct.unpack(">LLQ", reply) 66 | assert(magic == 0x67446698) 67 | assert(handle == self._handle) 68 | self._handle += 1 69 | if self._is_read: 70 | data = self._s.recv(self._length) 71 | return (data, errno) 72 | 73 | def _check_value(self, name, value): 74 | if not value % 512: 75 | return 76 | raise ValueError("%s=%i is not a multiple of 512" % (name, value)) 77 | 78 | def write(self, data, offset): 79 | self._check_value("offset", offset) 80 | self._check_value("size", len(data)) 81 | self._flushed = False 82 | self._is_read = False 83 | header = self._build_header(self.WRITE, offset, len(data)) 84 | self._s.send(header + data) 85 | (data, errno) = self._parse_reply() 86 | assert(errno == 0) 87 | return len(data) 88 | 89 | def read(self, offset, length): 90 | self._check_value("offset", offset) 91 | self._check_value("length", length) 92 | header = self._build_header(self.READ, offset, length) 93 | self._is_read = True 94 | self._length = length 95 | self._s.send(header) 96 | (data, errno) = self._parse_reply() 97 | assert(errno == 0) 98 | return data 99 | 100 | def need_flush(self): 101 | if self._flags & self.FLAG_HAS_FLAGS != 0 and \ 102 | self._flags & self.FLAG_SEND_FLUSH != 0: 103 | return True 104 | else: 105 | return False 106 | 107 | def flush(self): 108 | self._is_read = False 109 | if self.need_flush() == False: 110 | self._flushed = True 111 | return True 112 | header = self._build_header(self.FLUSH, 0, 0) 113 | self._s.send(header) 114 | (data, errno) = self._parse_reply() 115 | self._handle += 1 116 | if not errno: 117 | self._flushed = True 118 | return errno == 0 119 | 120 | def _disconnect(self): 121 | self._is_read = False 122 | header = self._build_header(self.DISCONNECT, 0, 0) 123 | self._s.send(header) 124 | 125 | def size(self): 126 | return self._size 127 | -------------------------------------------------------------------------------- /backend/unikernel/rumprun/__init__.py: -------------------------------------------------------------------------------- 1 | """Implements interface for Rumprun unikernels.""" 2 | 3 | 4 | from backend.vm import VMConfig 5 | from os import path 6 | 7 | 8 | class Rumprun: 9 | cmdline_template = '''{{,, 10 | "blk" : {{,, 11 | "source": "dev",, 12 | "path": "/dev/ld0a",, 13 | "fstype": "blk",, 14 | "mountpoint": "/data",, 15 | }},, 16 | "net" : {{,, 17 | "if": "vioif0",, 18 | "type": "inet",, 19 | "method": "static",, 20 | "addr": "{ipv4_addr}",, 21 | "mask": "24",, 22 | }},, 23 | "cmdline": "{extra_cmdline}",, 24 | }},, 25 | ''' 26 | 27 | @staticmethod 28 | def configure(image, config, nic_name): 29 | cmdline = Rumprun.cmdline_template.format( 30 | ipv4_addr=config.ipv4_addr, 31 | extra_cmdline=config.cmdline if config.cmdline else image.default_cmdline, 32 | ) 33 | vmc = VMConfig( 34 | name=config.name, 35 | cmdline=cmdline, 36 | nic_name=nic_name, 37 | vdisk_path=path.join(image.root, 'data.iso'), 38 | memory_size=1024000 39 | ) 40 | vmc.kernel_path = path.join(image.root, 'kernel.bin') 41 | return vmc 42 | -------------------------------------------------------------------------------- /backend/unikernel/utils.py: -------------------------------------------------------------------------------- 1 | from backend.vm import VMConfig 2 | from api.models.cunik import CunikConfig 3 | 4 | 5 | def configure_common(cucfg: CunikConfig): 6 | pass 7 | -------------------------------------------------------------------------------- /backend/vm/__init__.py: -------------------------------------------------------------------------------- 1 | """Unified vm interface.""" 2 | from .vm import * 3 | -------------------------------------------------------------------------------- /backend/vm/vm.py: -------------------------------------------------------------------------------- 1 | import libvirt as lv 2 | import xml.etree.cElementTree as ET 3 | import sys, os 4 | 5 | 6 | class InvalidVMConfigError(Exception): 7 | def __str__(self): 8 | return 'Invalid VM config, perhaps some vital parameters haven\'t been set.' 9 | 10 | 11 | class UnknownHypervisorError(Exception): 12 | def __init__(self, hv): 13 | self.__hv = hv 14 | 15 | def __str__(self): 16 | return 'Unknown hypervisor {0}, not in {1}.'.format(self.__hv, VMConfig.available_hypervisors) 17 | 18 | 19 | class VMConfig: 20 | """The handy representation of VM configuration. 21 | 22 | """ 23 | def __init__(self, name, nic_name=None, kernel_path=None, cmdline=None, 24 | num_cpus=1, memory_size=4096, vdisk_path=None, vdisk_format='raw', 25 | hypervisor='kvm'): 26 | self.name = name 27 | self.kernel_path = kernel_path 28 | self.cmdline = cmdline 29 | self.num_cpus = num_cpus 30 | self.memory_size = memory_size 31 | self.vdisk_path = vdisk_path 32 | self.vdisk_format = vdisk_format 33 | self.nic_name = nic_name 34 | self.hypervisor = hypervisor 35 | 36 | def to_xml(self): 37 | """Generate XML representation for libvirt. 38 | 39 | Raises: 40 | InvalidVMConfigError 41 | """ 42 | domain = ET.Element('domain') 43 | domain.set('type', self.hypervisor) 44 | 45 | name = ET.SubElement(domain, 'name') 46 | name.text = self.name 47 | 48 | os = ET.SubElement(domain, 'os') 49 | tp = ET.SubElement(os, 'type') 50 | tp.text = 'hvm' 51 | if self.kernel_path: 52 | kernel = ET.SubElement(os, 'kernel') 53 | kernel.text = self.kernel_path 54 | cmdline = ET.SubElement(os, 'cmdline') 55 | cmdline.text = 'console=ttyS0 ' + self.cmdline 56 | 57 | vcpu = ET.SubElement(domain, 'vcpu') 58 | vcpu.set('placement', 'static') 59 | vcpu.text = str(self.num_cpus) 60 | 61 | memory = ET.SubElement(domain, 'memory') 62 | memory.text = str(self.memory_size) 63 | 64 | devices = ET.SubElement(domain, 'devices') 65 | 66 | # Disks 67 | if self.vdisk_path: 68 | disk = ET.SubElement(devices, 'disk') 69 | disk.set('type', 'file') 70 | disk.set('device', 'disk') 71 | source = ET.SubElement(disk, 'source') 72 | source.set('file', self.vdisk_path) 73 | target = ET.SubElement(disk, 'target') 74 | target.set('dev', 'vda') 75 | target.set('bus', 'virtio') 76 | driver = ET.SubElement(disk, 'driver') 77 | driver.set('type', self.vdisk_format) 78 | driver.set('name', 'qemu') 79 | # readonly = ET.SubElement(disk, 'readonly') # needed for qemu >= 2.10, for its image locking feature. 80 | 81 | # NIC 82 | # TODO: not recommended by libvirt 83 | if self.nic_name: 84 | ethernet = ET.SubElement(devices, 'interface') 85 | ethernet.set('type', 'ethernet') 86 | target = ET.SubElement(ethernet, 'target') 87 | target.set('dev', self.nic_name) 88 | model = ET.SubElement(ethernet, 'model') 89 | model.set('type', 'virtio') 90 | driver = ET.SubElement(ethernet, 'driver') 91 | driver.set('name', 'qemu') 92 | 93 | # Memballoon not supported, so none 94 | memballoon = ET.SubElement(devices, 'memballoon') 95 | memballoon.set('model', 'none') 96 | 97 | # Features 98 | features = ET.SubElement(domain, 'features') 99 | acpi = ET.SubElement(features, 'acpi') 100 | 101 | # For debugging 102 | serial = ET.SubElement(devices, 'serial') 103 | serial.set('type', 'pty') 104 | target = ET.SubElement(serial, 'target') 105 | target.set('port', '0') 106 | console = ET.SubElement(devices, 'console') 107 | console.set('type', 'pty') 108 | target = ET.SubElement(console, 'target') 109 | target.set('port', '0') 110 | target.set('type', 'serial') 111 | 112 | return ET.tostring(domain).decode() 113 | 114 | 115 | class VM: 116 | """Refer to a vm. 117 | 118 | All the public methods of this class will immediately 119 | affect virtual machine unless it raises an exception. 120 | 121 | Usage: 122 | >>> vmc = VMConfig() 123 | >>> # ... 124 | >>> vm = VM(vmc) # Now there is a new cunik in cunik registry along with the vm instance 125 | >>> uuid = vm.uuid # Unique between all hosts, can be used to identify 126 | >>> vm.start() 127 | >>> vm.stop() 128 | >>> del vm # Now this vm disappears 129 | """ 130 | 131 | def __init__(self, config=None): 132 | # TODO: should we define then start or just create? 133 | conn = lv.open('') 134 | if conn is None: 135 | print('[ERROR] Failed to open connection to qemu:///system', file=sys.stderr) 136 | self.domain = conn.defineXML(config.to_xml()) 137 | self.uuid = self.domain.UUIDString() 138 | conn.close() 139 | 140 | def start(self): 141 | """Start the vm, may raise exception.""" 142 | if self.domain.isActive(): 143 | self.domain.resume() 144 | else: 145 | self.domain.create() 146 | 147 | def stop(self): 148 | # This is necessary because the vm may not be running 149 | try: 150 | self.domain.suspend() 151 | except lv.libvirtError: 152 | pass 153 | 154 | def destroy(self): 155 | # This is necessary because the vm may not be running 156 | self.domain.undefine() 157 | try: 158 | self.domain.destroy() 159 | except: 160 | pass 161 | 162 | @staticmethod 163 | def from_json(vm_json: dict): 164 | res = VM() 165 | res.uuid = vm_json['uuid'] 166 | conn = lv.open('') 167 | if conn is None: 168 | print('[ERROR] Failed to open connection to qemu:///system', file=sys.stderr) 169 | try: 170 | res.domain = conn.lookupByUUIDString(res.uuid) 171 | except lv.libvirtError: 172 | print('[ERROR] VM instance with UUID={} not found'.format(res.uuid), file=sys.stderr) 173 | raise KeyError 174 | if res.domain is None: 175 | print('[ERROR] Failed to find the domain with UUID={}'.format(res.uuid), file=sys.stderr) 176 | conn.close() 177 | return res 178 | 179 | def to_json(self): 180 | return {'uuid': self.uuid} 181 | -------------------------------------------------------------------------------- /dev/start_cunik.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.system('sudo ./venv/bin/python -u ./dev/{}'.format('test_nginx.py')) 4 | -------------------------------------------------------------------------------- /dev/start_vm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ip l del tap0 2>/dev/null 5 | 6 | ip tuntap add tap0 mode tap 7 | ip addr add 10.0.120.100/24 dev tap0 8 | ip link set dev tap0 up 9 | 10 | python3 dev/test_vm.py 11 | -------------------------------------------------------------------------------- /dev/test_nginx.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | sys.path.append(os.path.abspath('.')) 6 | 7 | from api.models.cunik import CunikApi 8 | 9 | CunikApi.create('nginx', params={'ipv4_addr': '10.0.120.101'}) 10 | print('created') 11 | time.sleep(2) 12 | print('waiting') 13 | os.system('curl 10.0.120.101') 14 | for i in CunikApi.list(): 15 | CunikApi.stop(i) 16 | print('stopped') 17 | for i in CunikApi.list(): 18 | CunikApi.remove(i) 19 | print('removed') 20 | -------------------------------------------------------------------------------- /dev/test_redis.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | sys.path.append(os.path.abspath('.')) 6 | 7 | from api.models.cunik import CunikApi 8 | 9 | L = [1, 2, 3, 4] 10 | 11 | ip_addr1 = '10.0.120.101' 12 | # ip_addr2 = '10.0.120.101' 13 | 14 | if 1 in L: 15 | CunikApi.create('redis-server-osv', params={'ipv4_addr': ip_addr1}) 16 | #CunikApi.create('redis-server-osv', params={'ipv4_addr': ip_addr2}) 17 | print('created') 18 | time.sleep(2) 19 | 20 | if 2 in L: 21 | os.system('redis-benchmark --csv -h {} -c 50 -n 1000 -P 16'.format(ip_addr1)) 22 | #os.system('redis-benchmark --csv -h {} -c 50 -n 1000 -P 16'.format(ip_addr2)) 23 | 24 | if 3 in L: 25 | for i in CunikApi.list(): 26 | CunikApi.stop(i) 27 | print('stopped') 28 | 29 | if 4 in L: 30 | for i in CunikApi.list(): 31 | CunikApi.remove(i) 32 | print('removed') 33 | -------------------------------------------------------------------------------- /dev/test_scalability.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | sys.path.append(os.path.abspath('.')) 6 | 7 | from api.models.cunik import CunikApi 8 | 9 | from api.config import default_config 10 | 11 | image_root = os.path.join(default_config.CUNIK_ROOT, 'images/nginx') 12 | 13 | T = 200 14 | 15 | delay_time = 2 16 | 17 | start_time = time.time() 18 | 19 | for i in range(1, T + 1): 20 | CunikApi.create('nginx', params={'ipv4_addr': '10.120.{}.101'.format(i)}) 21 | print('Started cunik{}'.format(i)) 22 | cnt = 0 23 | 24 | end_time = time.time() 25 | 26 | print('Time elapsed: {} sec'.format(end_time - start_time - delay_time)) 27 | 28 | input('All running') 29 | 30 | for i in CunikApi.list(): 31 | CunikApi.stop(i) 32 | 33 | print('All stopped') 34 | 35 | for i in CunikApi.list(): 36 | CunikApi.remove(i) 37 | 38 | print('All removed') 39 | -------------------------------------------------------------------------------- /dev/test_vm.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.abspath('.')) 4 | 5 | import backend.vm as V 6 | 7 | from api.config import default_config 8 | 9 | images_root = os.path.join(default_config.CUNIK_ROOT, 'images/nginx') 10 | volumes_root = os.path.join(default_config.CUNIK_ROOT, 'volumes/nginx') 11 | 12 | conf = V.VMConfig() 13 | conf.name = 'Cunik_by_VM' 14 | conf.kernel_path = os.path.join(images_root, 'kernel.bin') 15 | conf.cmdline = '''{,, 16 | "blk" : {,, 17 | "source": "dev",, 18 | "path": "/dev/ld0a",, 19 | "fstype": "blk",, 20 | "mountpoint": "/data",, 21 | },, 22 | "net" : {,, 23 | "if": "vioif0",, 24 | "type": "inet",, 25 | "method": "static",, 26 | "addr": "10.0.120.101",, 27 | "mask": "24",, 28 | },, 29 | "cmdline": "./nginx.bin -c /data/conf/nginx.conf",, 30 | },,''' 31 | conf.memory_size = 256*1024 # 256 MB 32 | conf.vdisk_path = os.path.join(volumes_root, 'rootfs.iso') 33 | conf.nic_name = 'tap0' 34 | conf.hypervisor = 'kvm' 35 | vm = V.VM(conf) 36 | input('Created!') 37 | try: 38 | vm.start() 39 | input('Started!') 40 | 41 | vm.stop() 42 | input('Stopped!') 43 | finally: 44 | del vm 45 | input('Destroyed!') 46 | -------------------------------------------------------------------------------- /dev/utils/gen_conf.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | tmp = { 4 | 'name': 'Cunik0', 5 | 'img': 'test_kernel', 6 | 'cmd': 'test_cmdline', 7 | 'par': '', 8 | 'vmm': 'kvm', 9 | 'mem': '409600', 10 | 'data_volume': 'test_volume', 11 | 'data_volume_mount_point': '/data', 12 | } 13 | 14 | print(json.dumps(tmp, indent=4, sort_keys=False)) 15 | -------------------------------------------------------------------------------- /dev/utils/gen_metainfo.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | tmp = { 4 | 'namespace': 'core', 5 | 'name': 'nginx', 6 | 'version': '0.0.1', 7 | 'description': 'Nginx Web server', 8 | 'unikernel': { 9 | 'name': 'rumpkernel', 10 | 'version': None, 11 | 'url': 'http://rumpkernel.org/', 12 | }, 13 | 'platforms': [ 14 | 'kvm' 15 | ] 16 | } 17 | 18 | print(json.dumps(tmp, indent=4, sort_keys=False)) 19 | -------------------------------------------------------------------------------- /dev/utils/gen_paras.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | tmp = { 4 | 'ipv4_addr': '10.0.120.101', 5 | 'extra_cmdline': './nginx.bin -c /data/conf/nginx.conf' 6 | } 7 | 8 | print(json.dumps(tmp, indent=4, sort_keys=False)) 9 | -------------------------------------------------------------------------------- /engine.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | from api import create_app 4 | from flask_script import Manager 5 | from tests import TestAll 6 | 7 | app = create_app('default') 8 | manager = Manager(app) 9 | 10 | manager.add_command('test', TestAll()) 11 | 12 | if __name__ == '__main__': 13 | manager.run() 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.7 2 | Flask==1.0.2 3 | Flask-Script==2.0.6 4 | itsdangerous==0.24 5 | Jinja2==2.10 6 | libvirt-python==4.3.0 7 | MarkupSafe==1.0 8 | Werkzeug==0.14.1 9 | pyroute2==0.5.2 10 | 11 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | 3 | __all__ = ["TestAll"] 4 | 5 | from .tests import TestAll -------------------------------------------------------------------------------- /tests/test_cunik_registry/__init__.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | """ 3 | @time : 2018/6/7 22:45 4 | @author : Jiyan He 5 | @file : cunik_runtime.py 6 | 7 | 8 | """ -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | # coding : utf-8 2 | from flask_script import Command 3 | import unittest 4 | 5 | class TestAll(Command): 6 | 7 | def run(self): 8 | self.test_all() 9 | 10 | def test_all(self): 11 | tests = unittest.TestLoader().discover('.') 12 | unittest.TextTestRunner(verbosity=1).run(tests) --------------------------------------------------------------------------------