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.
- nova_bhyve-0.9.0/PKG-INFO +57 -0
- nova_bhyve-0.9.0/README.md +47 -0
- nova_bhyve-0.9.0/pyproject.toml +25 -0
- nova_bhyve-0.9.0/pyproject.toml.orig +19 -0
- nova_bhyve-0.9.0/src/nova/virt/bhyve/__init__.py +1 -0
- nova_bhyve-0.9.0/src/nova/virt/bhyve/driver.py +5 -0
- nova_bhyve-0.9.0/src/nova/virt/bhyve/privsep.py +133 -0
- nova_bhyve-0.9.0/src/nova_bhyve/__init__.py +1 -0
- nova_bhyve-0.9.0/src/nova_bhyve/conf.py +77 -0
- nova_bhyve-0.9.0/src/nova_bhyve/driver.py +1106 -0
- nova_bhyve-0.9.0/src/nova_bhyve/exception.py +23 -0
- nova_bhyve-0.9.0/src/nova_bhyve/images.py +488 -0
- nova_bhyve-0.9.0/src/nova_bhyve/machine/__init__.py +110 -0
- nova_bhyve-0.9.0/src/nova_bhyve/machine/direct.py +501 -0
- nova_bhyve-0.9.0/src/nova_bhyve/storage/__init__.py +1 -0
- nova_bhyve-0.9.0/src/nova_bhyve/storage/zfs.py +222 -0
- nova_bhyve-0.9.0/src/nova_bhyve/supervisor.py +149 -0
|
@@ -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,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)]
|