nova-bhyve 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.3
2
+ Name: nova-bhyve
3
+ Version: 0.9.0
4
+ Summary: Bhyve virtualization driver for OpenStack Nova
5
+ Author: joshiggins
6
+ Author-email: joshiggins <josh@joshh.info>
7
+ License: Apache-2.0
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+
11
+ # nova-bhyve
12
+
13
+ An OpenStack Nova compute driver that runs instances as [bhyve](https://bhyve.org/) virtual machines on FreeBSD.
14
+
15
+ ## Status
16
+
17
+ ### Features
18
+
19
+ - Direct VM management through `bhyve`/`bhyvectl`
20
+ - Instance root disks on ZFS
21
+ - Supports booting from qcow2 and raw Glance images
22
+ - Glance images cached as zvols with zero copy root disk cloning
23
+ - VNC console integration through `nova-novncproxy`
24
+ - Cold attach of cinder volumes
25
+ - Instance snapshot and upload to Glance
26
+
27
+ ### Roadmap
28
+
29
+ - Cold attach of network interfaces
30
+ - Cold migration between hosts (currently only supports same-host migration i.e. instance resize)
31
+ - Automatically remove cached images once last instance is gone
32
+ - Support for additional ephemeral/swap disks
33
+
34
+ ### Limitations
35
+
36
+ - Live migration is not supported yet by bhyve, so is not implemented in this driver
37
+
38
+ ## Host requirements
39
+
40
+ `zfs allow` delegation is needed for the dataset holding images and instances.
41
+
42
+ A `devfs.rules` entry is needed to grant the group running Nova read/write access to the zvol device nodes.
43
+
44
+ You must configure `volmode=dev` on the dataset so that the host does not publish guest partition tables into its own `/dev`.
45
+
46
+ ## Configuration
47
+
48
+ Example configuration for Nova:
49
+
50
+ ```
51
+ [DEFAULT]
52
+ compute_driver = bhyve.driver.BhyveDriver
53
+ allow_resize_to_same_host = True
54
+
55
+ [bhyve]
56
+ zfs_dataset = zroot/nova
57
+ ```
@@ -0,0 +1,47 @@
1
+ # nova-bhyve
2
+
3
+ An OpenStack Nova compute driver that runs instances as [bhyve](https://bhyve.org/) virtual machines on FreeBSD.
4
+
5
+ ## Status
6
+
7
+ ### Features
8
+
9
+ - Direct VM management through `bhyve`/`bhyvectl`
10
+ - Instance root disks on ZFS
11
+ - Supports booting from qcow2 and raw Glance images
12
+ - Glance images cached as zvols with zero copy root disk cloning
13
+ - VNC console integration through `nova-novncproxy`
14
+ - Cold attach of cinder volumes
15
+ - Instance snapshot and upload to Glance
16
+
17
+ ### Roadmap
18
+
19
+ - Cold attach of network interfaces
20
+ - Cold migration between hosts (currently only supports same-host migration i.e. instance resize)
21
+ - Automatically remove cached images once last instance is gone
22
+ - Support for additional ephemeral/swap disks
23
+
24
+ ### Limitations
25
+
26
+ - Live migration is not supported yet by bhyve, so is not implemented in this driver
27
+
28
+ ## Host requirements
29
+
30
+ `zfs allow` delegation is needed for the dataset holding images and instances.
31
+
32
+ A `devfs.rules` entry is needed to grant the group running Nova read/write access to the zvol device nodes.
33
+
34
+ You must configure `volmode=dev` on the dataset so that the host does not publish guest partition tables into its own `/dev`.
35
+
36
+ ## Configuration
37
+
38
+ Example configuration for Nova:
39
+
40
+ ```
41
+ [DEFAULT]
42
+ compute_driver = bhyve.driver.BhyveDriver
43
+ allow_resize_to_same_host = True
44
+
45
+ [bhyve]
46
+ zfs_dataset = zroot/nova
47
+ ```
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "nova-bhyve"
3
+ version = "0.9.0"
4
+ description = "Bhyve virtualization driver for OpenStack Nova"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = []
8
+
9
+ [[project.authors]]
10
+ name = "joshiggins"
11
+ email = "josh@joshh.info"
12
+
13
+ [project.license]
14
+ text = "Apache-2.0"
15
+
16
+ [build-system]
17
+ requires = ["uv_build>=0.11.28,<0.12.0"]
18
+ build-backend = "uv_build"
19
+
20
+ [tool.uv.build-backend]
21
+ module-name = [
22
+ "nova_bhyve",
23
+ "nova.virt.bhyve",
24
+ ]
25
+ namespace = true
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "nova-bhyve"
3
+ version = "0.9.0"
4
+ description = "Bhyve virtualization driver for OpenStack Nova"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "joshiggins", email = "josh@joshh.info" }
8
+ ]
9
+ license = {text = "Apache-2.0"}
10
+ requires-python = ">=3.12"
11
+ dependencies = []
12
+
13
+ [build-system]
14
+ requires = ["uv_build>=0.11.28,<0.12.0"]
15
+ build-backend = "uv_build"
16
+
17
+ [tool.uv.build-backend]
18
+ module-name = ["nova_bhyve", "nova.virt.bhyve"]
19
+ namespace = true
@@ -0,0 +1 @@
1
+ """Namespace shim that makes the driver importable as ``nova.virt.bhyve``."""
@@ -0,0 +1,5 @@
1
+ """Re-export of the bhyve driver."""
2
+
3
+ from nova_bhyve.driver import BhyveDriver
4
+
5
+ __all__ = ["BhyveDriver"]
@@ -0,0 +1,133 @@
1
+ """Privileged operations for the bhyve driver."""
2
+
3
+ import os
4
+ import re
5
+ import signal
6
+ import sys
7
+
8
+ import nova.privsep
9
+ from oslo_concurrency import processutils
10
+ from oslo_log import log as logging
11
+
12
+ LOG = logging.getLogger(__name__)
13
+
14
+ # TODO: these are defined in multiple places across the codebase. we should reuse from the main pkg
15
+ BHYVE = "/usr/sbin/bhyve"
16
+ BHYVECTL = "/usr/sbin/bhyvectl"
17
+ DAEMON = "/usr/sbin/daemon"
18
+
19
+ VMM_DIR = "/dev/vmm"
20
+
21
+ # TODO: should this be a validate_uuid helper?
22
+ UUID_RE = re.compile(r"^[0-9a-fA-F-]{36}$")
23
+
24
+
25
+ def check_name(name):
26
+ """Raise unless a name is shaped like an instance UUID."""
27
+ if not UUID_RE.match(name):
28
+ raise ValueError("refusing to act on %r: not an instance uuid" % name)
29
+ return name
30
+
31
+
32
+ def check_state_dir(state_dir, name):
33
+ """Raise unless a state directory is absolute and belongs to this VM."""
34
+ check_name(name)
35
+ if not os.path.isabs(state_dir) or os.path.basename(state_dir.rstrip("/")) != name:
36
+ raise ValueError(
37
+ "refusing to use %r as the state directory for %s" % (state_dir, name)
38
+ )
39
+ return state_dir
40
+
41
+
42
+ def pid_from(path):
43
+ """Read a pidfile, returning None if it is absent or unreadable."""
44
+ try:
45
+ with open(path) as handle:
46
+ return int(handle.read().strip())
47
+ except (OSError, ValueError):
48
+ return None
49
+
50
+
51
+ @nova.privsep.sys_admin_pctxt.entrypoint
52
+ def start_domain(state_dir, name, argv, console_log=None):
53
+ """Start a guest under a nova_bhyve.supervisor process."""
54
+ check_state_dir(state_dir, name)
55
+ if not all(isinstance(a, str) for a in argv):
56
+ raise ValueError("bhyve arguments must all be strings")
57
+
58
+ cmd = [DAEMON, "-f", "-p", os.path.join(state_dir, "supervisor.pid")]
59
+ if console_log:
60
+ # the serial device is configured by the driver as stdio, so the log
61
+ # from daemon(8) is literally the console log ;-)
62
+ cmd += ["-o", console_log, "-m", "3", "-M", "0640"]
63
+ cmd += [
64
+ "--",
65
+ sys.executable,
66
+ "-m",
67
+ "nova_bhyve.supervisor",
68
+ "--state-dir",
69
+ state_dir,
70
+ "--name",
71
+ name,
72
+ "--",
73
+ *argv,
74
+ ]
75
+
76
+ LOG.info("bhyve privsep: starting %(name)s under a supervisor", {"name": name})
77
+ processutils.execute(*cmd)
78
+
79
+
80
+ @nova.privsep.sys_admin_pctxt.entrypoint
81
+ def stop_domain(state_dir, name, graceful=True):
82
+ """Signal a running guest, returning whether there was one to signal."""
83
+ check_state_dir(state_dir, name)
84
+
85
+ # send SIGTERM to the supervisor (ACPI shutdown)
86
+ sup_pid = pid_from(os.path.join(state_dir, "supervisor.pid"))
87
+ if sup_pid:
88
+ try:
89
+ os.kill(sup_pid, signal.SIGTERM if graceful else signal.SIGUSR1)
90
+ return True
91
+ except ProcessLookupError:
92
+ pass
93
+
94
+ # supervisor is dead, try to kill bhyve directly
95
+ pid = pid_from(os.path.join(state_dir, "bhyve.pid"))
96
+ if pid is None:
97
+ return False
98
+ try:
99
+ os.kill(pid, signal.SIGTERM if graceful else signal.SIGKILL)
100
+ except ProcessLookupError:
101
+ return False
102
+ return True
103
+
104
+
105
+ @nova.privsep.sys_admin_pctxt.entrypoint
106
+ def reap_vm(name):
107
+ """Reclaim a VM object left behind in /dev/vmm."""
108
+ check_name(name)
109
+ if not os.path.exists(os.path.join(VMM_DIR, name)):
110
+ return False
111
+ LOG.info("bhyve privsep: reclaiming the vmm object for %s", name)
112
+ try:
113
+ processutils.execute(BHYVECTL, "--destroy", "--vm=%s" % name)
114
+ except processutils.ProcessExecutionError as err:
115
+ LOG.warning(
116
+ "bhyve privsep: could not reclaim %(name)s: %(err)s",
117
+ {"name": name, "err": err},
118
+ )
119
+ return False
120
+ return True
121
+
122
+
123
+ @nova.privsep.sys_admin_pctxt.entrypoint
124
+ def vm_stats(name):
125
+ """Return bhyvectl's per-vcpu statistics as raw text, or None."""
126
+ check_name(name)
127
+ if not os.path.exists(os.path.join(VMM_DIR, name)):
128
+ return None
129
+ try:
130
+ out, _err = processutils.execute(BHYVECTL, "--vm=%s" % name, "--get-stats")
131
+ except processutils.ProcessExecutionError:
132
+ return None
133
+ return out
@@ -0,0 +1 @@
1
+ """Nova compute driver for bhyve."""
@@ -0,0 +1,77 @@
1
+ """Configuration options for the bhyve compute driver."""
2
+
3
+ from oslo_config import cfg
4
+
5
+ bhyve_group = cfg.OptGroup(
6
+ "bhyve",
7
+ title="bhyve driver options",
8
+ help="Options for the bhyve compute driver.",
9
+ )
10
+
11
+ bhyve_opts = [
12
+ cfg.StrOpt(
13
+ "zfs_dataset",
14
+ help="Dataset under which the driver creates its zvols, e.g. "
15
+ '"zroot/nova". Required: the driver refuses to start if this '
16
+ "is unset or if the dataset (or its images/ and instances/ "
17
+ "children) is missing. The nova user must hold ZFS delegated "
18
+ "administration on it.",
19
+ ),
20
+ cfg.StrOpt(
21
+ "mechanism",
22
+ default="direct",
23
+ choices=("direct",),
24
+ help="Mechansim for interacting with bhyve.",
25
+ ),
26
+ cfg.StrOpt(
27
+ "bootrom_path",
28
+ default="/usr/local/share/edk2-bhyve/BHYVE_UEFI_CODE.fd",
29
+ help="UEFI firmware image used for bhyve bootrom.",
30
+ ),
31
+ cfg.StrOpt(
32
+ "nvram_template",
33
+ default="/usr/local/share/edk2-bhyve/BHYVE_UEFI_VARS.fd",
34
+ help="Template for instance UEFI variables."
35
+ ),
36
+ cfg.IntOpt(
37
+ "destroy_timeout",
38
+ default=10,
39
+ min=1,
40
+ help="Seconds to wait for a guest to disappear after a hard stop "
41
+ "before giving up and reclaiming its resources anyway."
42
+ ),
43
+ cfg.StrOpt(
44
+ "image_scratch_dir",
45
+ default="$state_path/bhyve-scratch",
46
+ help="Directory for spool files while converting qcow2 to raw.",
47
+ ),
48
+ cfg.StrOpt(
49
+ "snapshot_image_format",
50
+ default="qcow2",
51
+ choices=("qcow2", "raw"),
52
+ help="Disk format for instance snapshots uploaded to Glance.\n"
53
+ "\n"
54
+ "qcow2 is the default so that snapshots are sparse.",
55
+ ),
56
+ cfg.BoolOpt(
57
+ "ignore_unimplemented_msr",
58
+ default=False,
59
+ help="Might help with running bhyve nested in a KVM guest.",
60
+ ),
61
+ cfg.IntOpt(
62
+ "shutdown_timeout_graceful",
63
+ default=30,
64
+ min=0,
65
+ help="Seconds to wait for an ACPI shutdown to complete before "
66
+ "destroying the domain.",
67
+ ),
68
+ ]
69
+
70
+ CONF = cfg.CONF
71
+ CONF.register_group(bhyve_group)
72
+ CONF.register_opts(bhyve_opts, group=bhyve_group)
73
+
74
+
75
+ def list_opts():
76
+ """Return the driver options for oslo-config-generator."""
77
+ return [(bhyve_group, bhyve_opts)]