|
| 1 | +import subprocess |
| 2 | +import time |
| 3 | +import urllib |
| 4 | + |
| 5 | +import requests |
| 6 | +import requests_unixsocket |
| 7 | +requests_unixsocket.monkeypatch() |
| 8 | + |
| 9 | + |
| 10 | +class Firecracker: |
| 11 | + """ |
| 12 | + Toy class to showcase using the Firecracker API. |
| 13 | + A full API client can be generated from the Firecracker open-api model. |
| 14 | + """ |
| 15 | + |
| 16 | + binary_name = 'firecracker' |
| 17 | + usocket_path_prefix = '/tmp/firecracker' |
| 18 | + |
| 19 | + machine_config_path = '/machine-config' |
| 20 | + network_ifaces_path = '/network-interfaces' |
| 21 | + drives_path = '/drives' |
| 22 | + vsocks_path = '/vsocks' |
| 23 | + boot_source_path = '/boot-source' |
| 24 | + actions_path = '/actions' |
| 25 | + |
| 26 | + def __init__(self, socket_name): |
| 27 | + self.socket_name = socket_name |
| 28 | + self.session_name = self.binary_name + socket_name |
| 29 | + |
| 30 | + def spawn(self): |
| 31 | + self.usocket_name = ( |
| 32 | + self.usocket_path_prefix + |
| 33 | + self.socket_name + |
| 34 | + '.socket' |
| 35 | + ) |
| 36 | + |
| 37 | + usocket_url = self.get_usocket_url() |
| 38 | + |
| 39 | + self.machine_config_url = usocket_url + self.machine_config_path |
| 40 | + self.network_ifaces_url = usocket_url + self.network_ifaces_path |
| 41 | + self.drives_url = usocket_url + self.drives_path |
| 42 | + self.vsocks_url = usocket_url + self.vsocks_path |
| 43 | + self.boot_source_url = usocket_url + self.boot_source_path |
| 44 | + self.actions_url = usocket_url + self.actions_path |
| 45 | + |
| 46 | + screen_cmd = ( |
| 47 | + 'screen -dmS ' + self.session_name + |
| 48 | + ' ./' + self.binary_name + ' --api-sock ' + self.usocket_name) |
| 49 | + subprocess.call(screen_cmd, shell=True) |
| 50 | + |
| 51 | + def get_usocket_url(self): |
| 52 | + url_encoded_prefix = urllib.parse.quote_plus(self.usocket_path_prefix) |
| 53 | + usocket_url = ( |
| 54 | + 'http+unix://' + |
| 55 | + url_encoded_prefix + |
| 56 | + self.socket_name + |
| 57 | + '.socket' |
| 58 | + ) |
| 59 | + return usocket_url |
| 60 | + |
| 61 | +# Spawn a new Firecracker Virtual Machine Manager process. |
| 62 | +firecracker = Firecracker('0001') |
| 63 | +firecracker.spawn() |
| 64 | + |
| 65 | +# Give the api time to come online since we don't handle retries here. |
| 66 | +time.sleep(0.0042) |
| 67 | + |
| 68 | +# Configure the microVM CPU and memory. |
| 69 | +requests.put(firecracker.machine_config_url, json={'vcpu_count': 2}) |
| 70 | +requests.put(firecracker.machine_config_url, json={'mem_size_mib': 256}) |
| 71 | + |
| 72 | +# Add a network interface to the microVM. |
| 73 | +# Firecracker will map this host network interface into the microVM. |
| 74 | +requests.put( |
| 75 | + firecracker.network_ifaces_url + '/1', |
| 76 | + json={ |
| 77 | + 'iface_id': '1', |
| 78 | + 'host_dev_name': 'fc0001tap1', |
| 79 | + 'state': 'Attached' |
| 80 | + } |
| 81 | +) |
| 82 | + |
| 83 | +# Add another network interface to the microVM. |
| 84 | +# Firecracker will map this host network interface into the microVM. |
| 85 | +requests.put( |
| 86 | + firecracker.network_ifaces_url + '/2', |
| 87 | + json={ |
| 88 | + 'iface_id': '2', |
| 89 | + 'host_dev_name': 'fc0001tap2', |
| 90 | + 'state': 'Attached' |
| 91 | + } |
| 92 | +) |
| 93 | + |
| 94 | +# Add a disk (block device) to the microVM. |
| 95 | +# This one will be flagged as the root file system. |
| 96 | +requests.put( |
| 97 | + firecracker.drives_url + '/1', |
| 98 | + json={ |
| 99 | + 'drive_id': '1', |
| 100 | + 'path_on_host': '/tmp/firecracker0001/ami-rootfs.ext4', |
| 101 | + 'state': 'Attached', |
| 102 | + 'is_root_device': True |
| 103 | + } |
| 104 | +) |
| 105 | + |
| 106 | +# Add another disk (block device) to the microVM. |
| 107 | +# This one is empty, usable for, e.g., guest scratch space. |
| 108 | +requests.put( |
| 109 | + firecracker.drives_url + '/2', |
| 110 | + json={ |
| 111 | + 'drive_id': '2', |
| 112 | + 'path_on_host': '/tmp/firecracker0001/scratch.ext4', |
| 113 | + 'state': 'Attached', |
| 114 | + 'is_root_device': False |
| 115 | + } |
| 116 | +) |
| 117 | + |
| 118 | +# Add a vsocket between the host and guest OSs (requiers both to be Linux). |
| 119 | +# Requires appropriate privileges, and both host and guest kernel support. |
| 120 | +requests.put( |
| 121 | + firecracker.vsocks_url + '/1', |
| 122 | + json={'vsock_id': '1', 'guest_cid': 10001, 'state': 'Attached'} |
| 123 | +) |
| 124 | + |
| 125 | +# Specify a boot source: a kernel image. |
| 126 | +# Currently, only linux kernel images are supported. |
| 127 | +requests.put( |
| 128 | + firecracker.boot_source_url, |
| 129 | + json={ |
| 130 | + 'boot_source_id': '1', |
| 131 | + 'source_type': 'LocalImage', |
| 132 | + 'local_image': {'kernel_image_path': '/tmp/vmlinux.bin'}, |
| 133 | + } |
| 134 | +) |
| 135 | + |
| 136 | +# Start! |
| 137 | +requests.put( |
| 138 | + firecracker.actions_url + '/1', |
| 139 | + json={'action_id': '1', 'action_type': 'InstanceStart'} |
| 140 | +) |
0 commit comments