├── .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 | [](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)
--------------------------------------------------------------------------------