Skip to content

Commit

Permalink
Allow bootstrapping virt VM with zvol (#15400)
Browse files Browse the repository at this point in the history
  • Loading branch information
Qubad786 authored Jan 15, 2025
1 parent 3e3b229 commit 927314b
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 5 deletions.
21 changes: 20 additions & 1 deletion src/middlewared/middlewared/api/v25_04_0/virt_instance.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from typing import Annotated, Literal, TypeAlias

from pydantic import Field, model_validator, StringConstraints
Expand Down Expand Up @@ -75,8 +76,8 @@ class VirtInstanceEntry(BaseModel):
@single_argument_args('virt_instance_create')
class VirtInstanceCreateArgs(BaseModel):
name: Annotated[NonEmptyString, StringConstraints(max_length=200)]
source_type: Literal[None, 'IMAGE', 'ISO'] = 'IMAGE'
iso_volume: NonEmptyString | None = None
source_type: Literal[None, 'IMAGE', 'ZVOL', 'ISO'] = 'IMAGE'
image: Annotated[NonEmptyString, StringConstraints(max_length=200)] | None = None
remote: REMOTE_CHOICES = 'LINUX_CONTAINERS'
instance_type: InstanceType = 'CONTAINER'
Expand All @@ -87,6 +88,12 @@ class VirtInstanceCreateArgs(BaseModel):
memory: MemoryType | None = None
enable_vnc: bool = False
vnc_port: int | None = Field(ge=5900, le=65535, default=None)
zvol_path: NonEmptyString | None = None
'''
This is useful when a VM wants to be booted where a ZVOL already has a VM bootstrapped in it and needs
to be ported over to virt plugin. Virt will consume this zvol and add it as a DISK device to the instance
with boot priority set to 1 so the VM can be booted from it.
'''

@model_validator(mode='after')
def validate_attrs(self):
Expand All @@ -95,15 +102,27 @@ def validate_attrs(self):
raise ValueError('Source type must be set to "IMAGE" when instance type is CONTAINER')
if self.enable_vnc:
raise ValueError('VNC is not supported for containers and `enable_vnc` should be unset')
if self.zvol_path:
raise ValueError('Zvol path is only supported for VMs')
else:
if self.enable_vnc and self.vnc_port is None:
raise ValueError('VNC port must be set when VNC is enabled')

if self.source_type == 'ISO' and self.iso_volume is None:
raise ValueError('ISO volume must be set when source type is "ISO"')

if self.source_type == 'ZVOL':
if self.zvol_path is None:
raise ValueError('Zvol path must be set when source type is "ZVOL"')
if self.zvol_path.startswith('/dev/zvol/') is False:
raise ValueError('Zvol path must be a valid zvol path')
elif not os.path.exists(self.zvol_path):
raise ValueError(f'Zvol path {self.zvol_path} does not exist')

if self.source_type == 'IMAGE' and self.image is None:
raise ValueError('Image must be set when source type is "IMAGE"')
elif self.source_type != 'IMAGE' and self.image:
raise ValueError('Image must not be set when source type is not "IMAGE"')

return self

Expand Down
23 changes: 19 additions & 4 deletions src/middlewared/middlewared/plugins/virt/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,21 +274,36 @@ async def do_create(self, job, data):
verrors = ValidationErrors()
await self.validate(data, 'virt_instance_create', verrors)

devices = {}
data_devices = data['devices'] or []
iso_volume = data.pop('iso_volume', None)
if data['source_type'] == 'ISO':
root_device_to_add = None
zvol_path = data.pop('zvol_path', None)
if data['source_type'] == 'ZVOL':
data['source_type'] = None
data_devices.append({
root_device_to_add = {
'name': 'ix_virt_zvol_root',
'dev_type': 'DISK',
'source': zvol_path,
'destination': None,
'readonly': False,
'boot_priority': 1,
}
elif data['source_type'] == 'ISO':
root_device_to_add = {
'name': iso_volume,
'dev_type': 'DISK',
'pool': 'default',
'source': iso_volume,
'destination': None,
'readonly': False,
'boot_priority': 1,
})
}

if root_device_to_add:
data['source_type'] = None
data_devices.append(root_device_to_add)

devices = {}
for i in data_devices:
await self.middleware.call(
'virt.instance.validate_device', i, 'virt_instance_create', verrors, data['instance_type'],
Expand Down
28 changes: 28 additions & 0 deletions tests/api2/test_virt_vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,34 @@ def test_vm_creation_with_iso_volume(vm, iso_volume):
call('virt.instance.delete', virt_instance_name, job=True)


def test_vm_creation_with_zvol(virt_pool, vm, iso_volume):
virt_instance_name = 'test-zvol-vm'
zvol_name = f'{virt_pool["pool"]}/test_zvol'
call('zfs.dataset.create', {
'name': zvol_name,
'type': 'VOLUME',
'properties': {'volsize': '514MiB'}
})
call('virt.instance.create', {
'name': virt_instance_name,
'instance_type': 'VM',
'source_type': 'ZVOL',
'zvol_path': f'/dev/zvol/{zvol_name}',
}, job=True)

try:
vm_devices = call('virt.instance.device_list', virt_instance_name)
assert any(
device['name'] == 'ix_virt_zvol_root'
and device['boot_priority'] == 1
for device in vm_devices
), vm_devices

finally:
call('virt.instance.delete', virt_instance_name, job=True)
call('zfs.dataset.delete', zvol_name)


@pytest.mark.parametrize('iso_volume,error_msg', [
(None, 'Value error, ISO volume must be set when source type is "ISO"'),
('test_iso123', 'Invalid ISO volume selected. Please select a valid ISO volume.'),
Expand Down

0 comments on commit 927314b

Please sign in to comment.