Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-133206 / 25.10 / Convert boot.* to versioned API #15524

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cecff19
add NotRequired default value
creatorcary Jan 22, 2025
28237fa
unit test
creatorcary Jan 22, 2025
78b43f1
add alias test case
creatorcary Jan 23, 2025
081e2bd
add expose_secrets test case
creatorcary Jan 23, 2025
570e125
ForUpdateMetaclass unit test
creatorcary Jan 23, 2025
d676cf0
Merge branch 'master' of https://github.com/truenas/middleware into n…
creatorcary Jan 23, 2025
0ba8013
use new model
creatorcary Jan 24, 2025
134def4
Move unit tests to github CI
creatorcary Jan 27, 2025
059d80c
validator has to be recursive
creatorcary Jan 27, 2025
2f8e339
fix test_excluded_field
creatorcary Jan 27, 2025
65939f8
recursion actually not necessary
creatorcary Jan 28, 2025
e2cfa2d
Merge branch 'master' of https://github.com/truenas/middleware into n…
creatorcary Jan 28, 2025
03dd44c
add roles, convert boot.format
creatorcary Jan 29, 2025
b449960
Merge branch 'master' of https://github.com/truenas/middleware into N…
creatorcary Jan 29, 2025
51e3ba4
missing comma
creatorcary Jan 29, 2025
07f1801
use BOOT_ENV_WRITE
creatorcary Jan 30, 2025
6e31ea5
Merge branch 'master' of https://github.com/truenas/middleware into N…
creatorcary Jan 30, 2025
c58588f
update roles
creatorcary Jan 30, 2025
711849d
fix usage of NotRequired
creatorcary Jan 30, 2025
aeafe7b
do NOT set new roles on private endpoints
creatorcary Jan 30, 2025
6b4b3a1
Merge branch 'master' of https://github.com/truenas/middleware into N…
creatorcary Feb 6, 2025
81785e1
move to 25.10
creatorcary Feb 6, 2025
55a39cd
don't version private endpoints
creatorcary Feb 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/middlewared/middlewared/api/base/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@


__all__ = ["BaseModel", "ForUpdateMetaclass", "query_result", "query_result_item", "added_event_model",
"changed_event_model", "removed_event_model", "single_argument_args", "single_argument_result"]
"changed_event_model", "removed_event_model", "single_argument_args", "single_argument_result",
"NotRequired", "NotRequiredModel"]


class BaseModel(PydanticBaseModel):
Expand Down Expand Up @@ -51,7 +52,7 @@ def model_dump(
exclude_none: bool = False,
round_trip: bool = False,
warnings: bool | typing.Literal['none', 'warn', 'error'] = True,
serialize_as_any: bool = False,
serialize_as_any: bool = True, # so that nested models set to `NotRequired` do not serialize
) -> dict[str, typing.Any]:
return self.__pydantic_serializer__.to_python(
self,
Expand Down Expand Up @@ -144,6 +145,23 @@ def serialize_model(self, serializer):
}


class NotRequiredModel(BaseModel):
@model_serializer(mode="wrap")
def serialize_basemodel(self, serializer):
obj = serializer(self)
if isinstance(obj, dict):
return {
k: v
for k, v in obj.items()
if v is not undefined
}
return obj


NotRequired = undefined
"""Use as the default value for fields that may be excluded from the model."""


def _field_for_update(field):
new = copy.deepcopy(field)
new.default = undefined
Expand Down
1 change: 1 addition & 0 deletions src/middlewared/middlewared/api/v25_04_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .app_ix_volume import * # noqa
from .app_registry import * # noqa
from .auth import * # noqa
from .boot import * # noqa
from .boot_environments import * # noqa
from .catalog import * # noqa
from .cloud_backup import * # noqa
Expand Down
94 changes: 94 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/boot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from typing import Literal

from pydantic import Field, PositiveInt

from middlewared.api.base import BaseModel, NotRequired, NotRequiredModel


__all__ = [
"BootGetDisksArgs", "BootGetDisksResult", "BootAttachArgs", "BootAttachResult", "BootDetachArgs",
"BootDetachResult", "BootReplaceArgs", "BootReplaceResult", "BootScrubArgs", "BootScrubResult",
"BootSetScrubIntervalArgs", "BootSetScrubIntervalResult", "BootUpdateInitramfsArgs", "BootUpdateInitramfsResult",
"BootFormatArgs", "BootFormatResult"
]


class BootAttachOptions(BaseModel):
expand: bool = False


class BootFormatOptions(NotRequiredModel):
size: int = NotRequired
legacy_schema: Literal["BIOS_ONLY", "EFI_ONLY", None] = None


class BootUpdateInitramfsOptions(BaseModel):
database: str | None = None
force: bool = False


class BootGetDisksArgs(BaseModel):
pass


class BootGetDisksResult(BaseModel):
result: list[str]


class BootAttachArgs(BaseModel):
dev: str
options: BootAttachOptions = Field(default_factory=BootAttachOptions)


class BootAttachResult(BaseModel):
result: None


class BootDetachArgs(BaseModel):
dev: str


class BootDetachResult(BaseModel):
result: None


class BootFormatArgs(BaseModel):
dev: str
options: BootFormatOptions = Field(default_factory=BootFormatOptions)


class BootFormatResult(BaseModel):
result: None


class BootReplaceArgs(BaseModel):
label: str
dev: str


class BootReplaceResult(BaseModel):
result: None


class BootScrubArgs(BaseModel):
pass


class BootScrubResult(BaseModel):
result: None


class BootSetScrubIntervalArgs(BaseModel):
interval: PositiveInt


class BootSetScrubIntervalResult(BaseModel):
result: PositiveInt


class BootUpdateInitramfsArgs(BaseModel):
options: BootUpdateInitramfsOptions = Field(default_factory=BootUpdateInitramfsOptions)


class BootUpdateInitramfsResult(BaseModel):
result: bool
44 changes: 15 additions & 29 deletions src/middlewared/middlewared/plugins/boot.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import asyncio
import os

from contextlib import asynccontextmanager
from middlewared.schema import accepts, Bool, Dict, Int, List, Str, returns, Patch
from middlewared.api import api_method
from middlewared.api.current import (
BootGetDisksArgs, BootGetDisksResult, BootAttachArgs, BootAttachResult, BootDetachArgs,
BootDetachResult, BootReplaceArgs, BootReplaceResult, BootScrubArgs, BootScrubResult,
BootSetScrubIntervalArgs, BootSetScrubIntervalResult, BootUpdateInitramfsArgs, BootUpdateInitramfsResult
)
from middlewared.schema import accepts, returns, Patch
from middlewared.service import CallError, Service, job, private
from middlewared.utils import run
from middlewared.utils.disks import valid_zfs_partition_uuids
from middlewared.validators import Range


BOOT_ATTACH_REPLACE_LOCK = 'boot_attach_replace'
BOOT_POOL_NAME = BOOT_POOL_DISKS = None
Expand Down Expand Up @@ -62,8 +67,7 @@ async def clear_disks_cache(self):
global BOOT_POOL_DISKS
BOOT_POOL_DISKS = None

@accepts(roles=['READONLY_ADMIN'])
@returns(List('disks', items=[Str('disk')]))
@api_method(BootGetDisksArgs, BootGetDisksResult, roles=['READONLY_ADMIN'])
creatorcary marked this conversation as resolved.
Show resolved Hide resolved
async def get_disks(self):
"""
Returns disks of the boot pool.
Expand All @@ -81,14 +85,7 @@ async def get_boot_type(self):
# https://wiki.debian.org/UEFI
return 'EFI' if os.path.exists('/sys/firmware/efi') else 'BIOS'

@accepts(
Str('dev'),
Dict(
'options',
Bool('expand', default=False),
),
)
@returns()
@api_method(BootAttachArgs, BootAttachResult, roles=['FULL_ADMIN'])
creatorcary marked this conversation as resolved.
Show resolved Hide resolved
@job(lock=BOOT_ATTACH_REPLACE_LOCK)
async def attach(self, job, dev, options):
"""
Expand Down Expand Up @@ -138,8 +135,7 @@ async def attach(self, job, dev, options):
await self.middleware.call('zfs.pool.online', BOOT_POOL_NAME, zfs_dev_part['name'], True)
await self.update_initramfs()

@accepts(Str('dev'))
@returns()
@api_method(BootDetachArgs, BootDetachResult, roles=['FULL_ADMIN'])
async def detach(self, dev):
"""
Detach given `dev` from boot pool.
Expand All @@ -148,8 +144,7 @@ async def detach(self, dev):
await self.middleware.call('zfs.pool.detach', BOOT_POOL_NAME, dev, {'clear_label': True})
await self.update_initramfs()

@accepts(Str('label'), Str('dev'))
@returns()
@api_method(BootReplaceArgs, BootReplaceResult, roles=['FULL_ADMIN'])
creatorcary marked this conversation as resolved.
Show resolved Hide resolved
@job(lock=BOOT_ATTACH_REPLACE_LOCK)
async def replace(self, job, label, dev):
"""
Expand Down Expand Up @@ -187,8 +182,7 @@ async def replace(self, job, label, dev):
await self.middleware.call('boot.install_loader', dev)
await self.update_initramfs()

@accepts()
@returns()
@api_method(BootScrubArgs, BootScrubResult, roles=['FULL_ADMIN'])
@job(lock='boot_scrub')
async def scrub(self, job):
"""
Expand All @@ -197,10 +191,7 @@ async def scrub(self, job):
subjob = await self.middleware.call('pool.scrub.scrub', BOOT_POOL_NAME)
return await job.wrap(subjob)

@accepts(
Int('interval', validators=[Range(min_=1)])
)
@returns(Int('interval'))
@api_method(BootSetScrubIntervalArgs, BootSetScrubIntervalResult, roles=['FULL_ADMIN'])
async def set_scrub_interval(self, interval):
"""
Set Automatic Scrub Interval value in days.
Expand All @@ -213,12 +204,7 @@ async def set_scrub_interval(self, interval):
)
return interval

@accepts(Dict(
'options',
Str('database', default=None, null=True),
Bool('force', default=False),
))
@private
@api_method(BootUpdateInitramfsArgs, BootUpdateInitramfsResult, roles=['FULL_ADMIN'], private=True)
async def update_initramfs(self, options):
"""
Returns true if initramfs was updated and false otherwise.
Expand Down
15 changes: 4 additions & 11 deletions src/middlewared/middlewared/plugins/boot_/format.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
from middlewared.schema import accepts, Dict, Int, Str
from middlewared.service import CallError, private, Service
from middlewared.api import api_method
from middlewared.api.current import BootFormatArgs, BootFormatResult
from middlewared.service import CallError, Service
from middlewared.utils import run


class BootService(Service):

@accepts(
Str('dev'),
Dict(
'options',
Int('size'),
Str('legacy_schema', enum=[None, 'BIOS_ONLY', 'EFI_ONLY'], null=True, default=None),
)
)
@private
@api_method(BootFormatArgs, BootFormatResult, roles=['FULL_ADMIN'], private=True)
creatorcary marked this conversation as resolved.
Show resolved Hide resolved
async def format(self, dev, options):
"""
Format a given disk `dev` using the appropriate partition layout
Expand Down
Loading