From: Waldemar Kozaczuk <[email protected]>
Committer: Waldemar Kozaczuk <[email protected]>
Branch: master

Refactor and enhance firefracker.py

This patch refactors scripts/firecracker.py to address
original review comments. It also enhances it to allow
setting up networking and block devices. Finally it
upgrades firecracker version to the latest 0.15.0.

Signed-off-by: Waldemar Kozaczuk <[email protected]>

---
diff --git a/scripts/firecracker.py b/scripts/firecracker.py
--- a/scripts/firecracker.py
+++ b/scripts/firecracker.py
@@ -7,11 +7,19 @@
 import json
 import subprocess
 import time
-from os.path import expanduser
+import argparse
+import re
 from datetime import datetime
 import requests_unixsocket


+verbose = False
+
+
+class ApiException(Exception):
+    pass
+
+
 class ApiClient(object):
     def __init__(self, domain_socket_path):
         self.socket_path = domain_socket_path
@@ -23,9 +31,8 @@ def api_socket_url(self, path):
     def make_put_call(self, path, request_body):
         url = self.api_socket_url(path)
         res = self.session.put(url, data=json.dumps(request_body))
-        print("%s: %s" % (path, res.status_code))
         if res.status_code != 204:
-            print(res.text)
+            raise ApiException(res.text)
         return res.status_code

     def create_instance(self, kernel_image_path, cmdline):
@@ -34,6 +41,21 @@ def create_instance(self, kernel_image_path, cmdline):
             'boot_args': cmdline
         })

+    def add_disk(self, disk_image_path):
+        self.make_put_call('/drives/rootfs', {
+            'drive_id': 'rootfs',
+            'path_on_host': disk_image_path,
+            'is_root_device': True,
+            'is_read_only': False
+        })
+
+    def add_network_interface(self, interface_name, host_interface_name, ):
+        self.make_put_call('/network-interfaces/%s' % interface_name, {
+            'iface_id': interface_name,
+            'host_dev_name': host_interface_name,
+            'guest_mac': "52:54:00:12:34:56"
+        })
+
     def start_instance(self):
         self.make_put_call('/actions', {
             'action_type': 'InstanceStart'
@@ -48,79 +70,186 @@ def configure_logging(self):
             "show_log_origin": True
         })

+    def configure_machine(self, vcpu_count, mem_size_in_mb):
+        self.make_put_call('/machine-config', {
+            'vcpu_count': vcpu_count,
+            'mem_size_mib': mem_size_in_mb
+        })
+

 def print_time(msg):
-    now = datetime.now()
-    print("%s: %s" % (now.strftime('%H:%M:%S.%f'), msg))
-
-
-# Check if firecracker is installed
-home_dir = expanduser("~")
-firecracker_path = os.path.join(home_dir, '.firecracker/firecracker')
-if os.environ.get('FIRECRACKER_PATH'):
-    firecracker_path = os.environ.get('FIRECRACKER_PATH')
-
-# And offer to install if not found
-if not os.path.exists(firecracker_path):
- download_url = 'https://github.com/firecracker-microvm/firecracker/releases/download/v0.14.0/firecracker-v0.14.0' - answer = raw_input("Firecracker executable has not been found under %s. " - "Would you like to download it from %s and place it under %s? [y|Y]" %
-                       (firecracker_path, download_url, firecracker_path))
-    if answer.capitalize() != 'Y':
-        print("Firecracker not available. Exiting ...")
-        sys.exit(-1)
-
-    directory = os.path.dirname(firecracker_path)
-    if not os.path.exists(directory):
-        os.mkdir(directory)
-    subprocess.call(['wget', download_url, '-O', firecracker_path])
-    os.chmod(firecracker_path, stat.S_IRUSR | stat.S_IXUSR)
-
-# Firecracker is installed so lets start
-print_time("Start")
-socket_path = '/tmp/firecracker.socket'
-
-# Delete socker file if exists
-if os.path.exists(socket_path):
-    os.unlink(socket_path)
-
-# Start firecracker process to communicate over specified UNIX socker file
-firecracker = subprocess.Popen([firecracker_path, '--api-sock', socket_path],
-                                 stdin=subprocess.PIPE, stdout=sys.stdout,
-                                 stderr=subprocess.STDOUT)
-
-# Prepare arguments we are going to pass when creating VM instance
-dirname = os.path.dirname(os.path.abspath(__file__))
-kernel_path = os.path.join(dirname, '../build/release/loader-stripped.elf')
-
-if len(sys.argv) > 1:
-    cmdline = sys.argv[1]
-else:
-    with open(os.path.join(dirname, '../build/release/cmdline'), 'r') as f:
-        cmdline = f.read()
-
-# Create API client and make API calls
-client = ApiClient(socket_path.replace("/", "%2F"))
-
-try:
-    # Very often on the very first run firecracker process
-    # is not ready yet to accept calls over socket file
-    # so we poll existence of this file as an good
-    # enough indicator of firecracker readyness
-    while not os.path.exists(socket_path):
-        time.sleep(0.01)
-    print_time("Firecracker ready")
-
-    client.create_instance(kernel_path, cmdline)
-    print_time("Created OSv VM")
-
-    client.start_instance()
-    print_time("Booted OSv VM")
-except Exception as e:
- print("Failed to run OSv on firecracker due to: ({0}): {1} !!!".format(e.errno, e.strerror))
-    firecracker.kill()
-    exit(-1)
-
-print_time("Waiting for firecracker process to terminate")
-firecracker.wait()
-print_time("End")
+    if verbose:
+        now = datetime.now()
+        print("%s: %s" % (now.isoformat(), msg))
+
+
+def setup_tap_interface(tap_interface_name, tap_ip):
+    # Setup tun tap interface if does not exist
+    tuntap_interfaces = subprocess.check_output(["ip", 'tuntap'])
+    if tuntap_interfaces.find(tap_interface_name) < 0:
+ print("The tap interface %s not found!. Needs to set it up" % tap_interface_name) + subprocess.call(['sudo', 'ip', 'tuntap', 'add', 'dev', tap_interface_name, 'mode', 'tap']) + subprocess.call(['sudo', 'sysctl', '-w', 'net.ipv4.conf.%s.proxy_arp=1' % tap_interface_name]) + subprocess.call(['sudo', 'sysctl', '-w', 'net.ipv6.conf.%s.disable_ipv6=1' % tap_interface_name]) + subprocess.call(['sudo', 'ip', 'addr', 'add', '%s/30' % tap_ip, 'dev', tap_interface_name]) + subprocess.call(['sudo', 'ip', 'link', 'set', 'dev', tap_interface_name, 'up'])
+
+
+def find_firecracker(dirname):
+    firecracker_path = os.path.join(dirname, '../.firecracker/firecracker')
+    if os.environ.get('FIRECRACKER_PATH'):
+        firecracker_path = os.environ.get('FIRECRACKER_PATH')
+
+    # And offer to install if not found
+    firecracker_version = 'v0.15.0'
+    if not os.path.exists(firecracker_path):
+ url_base = 'https://github.com/firecracker-microvm/firecracker/releases/download' + download_url = '%s/%s/firecracker-%s' % (url_base, firecracker_version, firecracker_version) + answer = raw_input("Firecracker executable has not been found under %s. " + "Would you like to download it from %s and place it under %s? [y|n]" % + (firecracker_path, download_url, firecracker_path))
+        if answer.capitalize() != 'Y':
+            print("Firecracker not available. Exiting ...")
+            sys.exit(-1)
+
+        directory = os.path.dirname(firecracker_path)
+        if not os.path.exists(directory):
+            os.mkdir(directory)
+        download_path = firecracker_path + '.download'
+        ret = subprocess.call(['wget', download_url, '-O', download_path])
+        if ret != 0:
+            print('Failed to download %s!' % download_url)
+            exit(-1)
+
+        subprocess.call(["strip", "-o", firecracker_path, download_path])
+        os.chmod(firecracker_path, stat.S_IRUSR | stat.S_IXUSR)
+        os.unlink(download_path)
+
+    return firecracker_path
+
+
+def disk_path(dirname):
+    qcow_disk_path = os.path.join(dirname, '../build/release/usr.img')
+    raw_disk_path = os.path.join(dirname, '../build/release/usr.raw')
+
+    # Firecracker is not able to use disk image files in QCOW format
+ # so we have to convert usr.img to raw format if the raw disk is missing
+    # or source qcow file is newer
+ if not os.path.exists(raw_disk_path) or os.path.getctime(qcow_disk_path) > os.path.getctime(raw_disk_path): + ret = subprocess.call(['qemu-img', 'convert', '-O', 'raw', qcow_disk_path, raw_disk_path])
+        if ret != 0:
+ print('Failed to convert %s to a raw format %s!' % (qcow_disk_path, raw_disk_path))
+            exit(-1)
+    return raw_disk_path
+
+
+def start_firecracker(firecracker_path, socket_path):
+    # Delete socket file if exists
+    if os.path.exists(socket_path):
+        os.unlink(socket_path)
+
+ # Start firecracker process to communicate over specified UNIX socket file
+    return subprocess.Popen([firecracker_path, '--api-sock', socket_path],
+                           stdin=subprocess.PIPE, stdout=sys.stdout,
+                           stderr=subprocess.STDOUT)
+
+
+def get_memory_size_in_mb(options):
+    memory_in_mb = 128
+    if options.memsize:
+        regex = re.search('(\d+[MG])', options.memsize)
+        if len(regex.groups()) > 0:
+            mem_size = regex.group(1)
+            memory_in_mb = int(mem_size[:-1])
+            if mem_size.endswith('G'):
+                memory_in_mb = memory_in_mb * 1024
+    return memory_in_mb
+
+
+def main(options):
+    # Check if firecracker is installed
+    dirname = os.path.dirname(os.path.abspath(__file__))
+    firecracker_path = find_firecracker(dirname)
+
+    # Firecracker is installed so lets start
+    print_time("Start")
+    socket_path = '/tmp/firecracker.socket'
+    firecracker = start_firecracker(firecracker_path, socket_path)
+
+    # Prepare arguments we are going to pass when creating VM instance
+ kernel_path = os.path.join(dirname, '../build/release/loader-stripped.elf')
+    raw_disk_path = disk_path(dirname)
+
+    cmdline = options.execute
+    if not cmdline:
+ with open(os.path.join(dirname, '../build/release/cmdline'), 'r') as f:
+            cmdline = f.read()
+
+    # Create API client and make API calls
+    client = ApiClient(socket_path.replace("/", "%2F"))
+
+    try:
+        # Very often on the very first run firecracker process
+        # is not ready yet to accept calls over socket file
+        # so we poll existence of this file as a good
+        # enough indicator if firecracker is ready
+        while not os.path.exists(socket_path):
+            time.sleep(0.01)
+        print_time("Firecracker ready")
+
+        memory_in_mb = get_memory_size_in_mb(options)
+        client.configure_machine(options.vcpus, memory_in_mb)
+        print_time("Configured VM")
+
+        client.add_disk(raw_disk_path)
+        print_time("Added disk")
+
+        cmdline = "--nopci %s" % cmdline
+        if options.networking:
+            tap_ip = '172.16.0.1'
+            setup_tap_interface('fc_tap0', tap_ip)
+            client.add_network_interface('eth0', 'fc_tap0')
+            client_ip = '172.16.0.2'
+ cmdline = '--ip=eth0,%s,255.255.255.252 --defaultgw=%s %s' % (client_ip, tap_ip, cmdline)
+        if options.verbose:
+            cmdline = '--verbose ' + cmdline
+
+        client.create_instance(kernel_path, cmdline)
+        print_time("Created OSv VM with cmdline: %s" % cmdline)
+
+        client.start_instance()
+        print_time("Booted OSv VM")
+
+    except ApiException as e:
+        print("Failed to make firecracker API call: %s." % e)
+        firecracker.kill()
+        exit(-1)
+
+    except Exception as e:
+ print("Failed to run OSv on firecracker due to: ({0}): {1} !!!".format(e.errno, e.strerror))
+        firecracker.kill()
+        exit(-1)
+
+    print_time("Waiting for firecracker process to terminate")
+    firecracker.wait()
+    print_time("End")
+
+
+if __name__ == "__main__":
+    # Parse arguments
+    parser = argparse.ArgumentParser(prog='firecracker')
+ parser.add_argument("-c", "--vcpus", action="store", type=int, default=1,
+                        help="specify number of vcpus")
+    parser.add_argument("-m", "--memsize", action="store", default="128M",
+                        help="specify memory: ex. 1G, 2G, ...")
+ parser.add_argument("-e", "--execute", action="store", default=None, metavar="CMD",
+                        help="overwrite command line")
+    parser.add_argument("-n", "--networking", action="store_true",
+ help="needs root to setup tap networking first time")
+    parser.add_argument("-V", "--verbose", action="store_true",
+ help="pass --verbose to OSv, to display more debugging information on the console")
+
+    cmd_args = parser.parse_args()
+    if cmd_args.verbose:
+        verbose = True
+    main(cmd_args)

--
You received this message because you are subscribed to the Google Groups "OSv 
Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to