Skip to content

Commit

Permalink
NAS-132813 / 25.04 / Convert pool.snapshottask to new API (#15164)
Browse files Browse the repository at this point in the history
* convert to new api

* validators must return to be used in new api

* Revert "validators must return to be used in new api"

This reverts commit c49f50d.

* address @themylogin and small refactor

* don't use old Time validator

* fix import
  • Loading branch information
creatorcary authored Dec 10, 2024
1 parent 1a4ae07 commit 8af0dc7
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 87 deletions.
3 changes: 1 addition & 2 deletions src/middlewared/middlewared/api/base/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
from pydantic.main import IncEx
from typing_extensions import Annotated

from middlewared.api.base.types.base import SECRET_VALUE
from middlewared.api.base.types.base.string import LongStringWrapper
from middlewared.api.base.types.string import SECRET_VALUE, LongStringWrapper
from middlewared.utils.lang import undefined


Expand Down
4 changes: 2 additions & 2 deletions src/middlewared/middlewared/api/base/types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .base import * # noqa
from .fc import * # noqa
from .filesystem import * # noqa
from .iscsi import * # noqa
from .string import * # noqa
from .user import * # noqa
from .filesystem import * # noqa

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
from pydantic_core import CoreSchema, core_schema, PydanticKnownError
from typing_extensions import Annotated

from middlewared.api.base.validators import time_validator
from middlewared.utils.netbios import validate_netbios_name, validate_netbios_domain
from middlewared.validators import Time
from zettarepl.snapshot.name import validate_snapshot_naming_schema

__all__ = ["HttpUrl", "LongString", "NonEmptyString", "LongNonEmptyString", "SECRET_VALUE", "TimeString", "NetbiosDomain", "NetbiosName"]

HttpUrl = Annotated[_HttpUrl, AfterValidator(str)]
__all__ = [
"HttpUrl", "LongString", "NonEmptyString", "LongNonEmptyString", "SECRET_VALUE", "TimeString", "NetbiosDomain",
"NetbiosName", "SnapshotNameSchema"
]


class LongStringWrapper:
Expand Down Expand Up @@ -47,17 +50,18 @@ def __get_pydantic_core_schema__(
)


HttpUrl = Annotated[_HttpUrl, AfterValidator(str)]
# By default, our strings are no more than 1024 characters long. This string is 2**31-1 characters long (SQLite limit).
LongString = Annotated[
LongStringWrapper,
BeforeValidator(LongStringWrapper),
PlainSerializer(lambda x: x.value if isinstance(x, LongStringWrapper) else x),
]

NonEmptyString = Annotated[str, Field(min_length=1)]
LongNonEmptyString = Annotated[LongString, Field(min_length=1)]
TimeString = Annotated[str, AfterValidator(Time())]
TimeString = Annotated[str, AfterValidator(time_validator)]
NetbiosDomain = Annotated[str, AfterValidator(validate_netbios_domain)]
NetbiosName = Annotated[str, AfterValidator(validate_netbios_name)]
SnapshotNameSchema = Annotated[str, AfterValidator(lambda val: validate_snapshot_naming_schema(val) or val)]

SECRET_VALUE = "********"
23 changes: 23 additions & 0 deletions src/middlewared/middlewared/api/base/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from datetime import time
import re


def match_validator(pattern: re.Pattern, explanation: str | None = None):
def validator(value: str):
assert (value is None or pattern.match(value)), (explanation or f"Value does not match {pattern!r} pattern")
return value

return validator


def time_validator(value: str):
try:
hours, minutes = value.split(':')
except ValueError:
raise ValueError('Time should be in 24 hour format like "18:00"')
else:
try:
time(int(hours), int(minutes))
except TypeError:
raise ValueError('Time should be in 24 hour format like "18:00"')
return value

This file was deleted.

10 changes: 0 additions & 10 deletions src/middlewared/middlewared/api/base/validators/string.py

This file was deleted.

3 changes: 2 additions & 1 deletion src/middlewared/middlewared/api/v25_04_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
from .iscsi_extent import * # noqa
from .keychain import * # noqa
from .netdata import * # noqa
from .pool_scrub import * # noqa
from .pool import * # noqa
from .pool_resilver import * # noqa
from .pool_scrub import * # noqa
from .pool_snapshottask import * # noqa
from .privilege import * # noqa
from .reporting import * # noqa
from .reporting_exporters import * # noqa
Expand Down
120 changes: 120 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/pool_snapshottask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from typing import Any, Literal

from pydantic import Field

from middlewared.api.base import BaseModel, ForUpdateMetaclass, TimeString, SnapshotNameSchema
from .common import CronModel


__all__ = [
"PoolSnapshotTaskEntry", "PoolSnapshotTaskCreateArgs", "PoolSnapshotTaskCreateResult",
"PoolSnapshotTaskUpdateArgs", "PoolSnapshotTaskUpdateResult", "PoolSnapshotTaskDeleteArgs",
"PoolSnapshotTaskDeleteResult", "PoolSnapshotTaskMaxCountArgs", "PoolSnapshotTaskMaxCountResult",
"PoolSnapshotTaskMaxTotalCountArgs", "PoolSnapshotTaskMaxTotalCountResult", "PoolSnapshotTaskRunArgs",
"PoolSnapshotTaskRunResult", "PoolSnapshotTaskUpdateWillChangeRetentionForArgs",
"PoolSnapshotTaskUpdateWillChangeRetentionForResult", "PoolSnapshotTaskDeleteWillChangeRetentionForArgs",
"PoolSnapshotTaskDeleteWillChangeRetentionForResult"
]


class PoolSnapshotTaskCron(CronModel):
minute: str = "00"
begin: TimeString = "00:00"
end: TimeString = "23:59"


class PoolSnapshotTaskCreate(BaseModel):
dataset: str
recursive: bool = False
lifetime_value: int = 2
lifetime_unit: Literal["HOUR", "DAY", "WEEK", "MONTH", "YEAR"] = "WEEK"
enabled: bool = True
exclude: list[str] = []
naming_schema: SnapshotNameSchema = "auto-%Y-%m-%d_%H-%M"
allow_empty: bool = True
schedule: PoolSnapshotTaskCron = Field(default_factory=PoolSnapshotTaskCron)


class PoolSnapshotTaskUpdate(PoolSnapshotTaskCreate, metaclass=ForUpdateMetaclass):
fixate_removal_date: bool


class PoolSnapshotTaskUpdateWillChangeRetentionFor(PoolSnapshotTaskCreate, metaclass=ForUpdateMetaclass):
pass


class PoolSnapshotTaskDeleteOptions(BaseModel):
fixate_removal_date: bool = False


class PoolSnapshotTaskEntry(PoolSnapshotTaskCreate):
id: int
vmware_sync: bool
state: Any


class PoolSnapshotTaskCreateArgs(BaseModel):
data: PoolSnapshotTaskCreate


class PoolSnapshotTaskCreateResult(BaseModel):
result: PoolSnapshotTaskEntry


class PoolSnapshotTaskUpdateArgs(BaseModel):
id: int
data: PoolSnapshotTaskUpdate


class PoolSnapshotTaskUpdateResult(BaseModel):
result: PoolSnapshotTaskEntry


class PoolSnapshotTaskDeleteArgs(BaseModel):
id: int
options: PoolSnapshotTaskDeleteOptions = Field(default_factory=PoolSnapshotTaskDeleteOptions)


class PoolSnapshotTaskDeleteResult(BaseModel):
result: Literal[True]


class PoolSnapshotTaskMaxCountArgs(BaseModel):
pass


class PoolSnapshotTaskMaxCountResult(BaseModel):
result: int


class PoolSnapshotTaskMaxTotalCountArgs(BaseModel):
pass


class PoolSnapshotTaskMaxTotalCountResult(BaseModel):
result: int


class PoolSnapshotTaskRunArgs(BaseModel):
id: int


class PoolSnapshotTaskRunResult(BaseModel):
result: None


class PoolSnapshotTaskUpdateWillChangeRetentionForArgs(BaseModel):
id: int
data: PoolSnapshotTaskUpdateWillChangeRetentionFor


class PoolSnapshotTaskUpdateWillChangeRetentionForResult(BaseModel):
result: dict[str, list[str]]


class PoolSnapshotTaskDeleteWillChangeRetentionForArgs(BaseModel):
id: int


class PoolSnapshotTaskDeleteWillChangeRetentionForResult(BaseModel):
result: dict[str, list[str]]
72 changes: 24 additions & 48 deletions src/middlewared/middlewared/plugins/snapshot.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from datetime import datetime, time, timedelta
from datetime import time
import os

from middlewared.api import api_method
from middlewared.api.current import (
PoolSnapshotTaskEntry, PoolSnapshotTaskCreateArgs, PoolSnapshotTaskCreateResult, PoolSnapshotTaskUpdateArgs,
PoolSnapshotTaskUpdateResult, PoolSnapshotTaskDeleteArgs, PoolSnapshotTaskDeleteResult,
PoolSnapshotTaskMaxCountArgs, PoolSnapshotTaskMaxCountResult, PoolSnapshotTaskMaxTotalCountArgs,
PoolSnapshotTaskMaxTotalCountResult, PoolSnapshotTaskRunArgs, PoolSnapshotTaskRunResult
)
from middlewared.common.attachment import FSAttachmentDelegate
from middlewared.schema import accepts, returns, Bool, Cron, Dataset, Dict, Int, List, Patch, Str
from middlewared.schema import Cron
from middlewared.service import CallError, CRUDService, item_method, private, ValidationErrors
import middlewared.sqlalchemy as sa
from middlewared.utils.cron import croniter_for_schedule
from middlewared.utils.path import is_child
from middlewared.validators import ReplicationSnapshotNamingSchema


class PeriodicSnapshotTaskModel(sa.Model):
Expand Down Expand Up @@ -40,6 +45,7 @@ class Config:
datastore_extend_context = 'pool.snapshottask.extend_context'
namespace = 'pool.snapshottask'
cli_namespace = 'task.snapshot'
entry = PoolSnapshotTaskEntry

@private
async def extend_context(self, rows, extra):
Expand Down Expand Up @@ -69,29 +75,9 @@ async def extend(self, data, context):

return data

@accepts(
Dict(
'periodic_snapshot_create',
Dataset('dataset', required=True),
Bool('recursive', required=True),
List('exclude', items=[Dataset('item')]),
Int('lifetime_value', required=True),
Str('lifetime_unit', enum=['HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR'], required=True),
Str('naming_schema', required=True, validators=[ReplicationSnapshotNamingSchema()]),
Cron(
'schedule',
defaults={
'minute': '00',
'begin': '00:00',
'end': '23:59',
},
required=True,
begin_end=True
),
Bool('allow_empty', default=True),
Bool('enabled', default=True),
register=True
),
@api_method(
PoolSnapshotTaskCreateArgs,
PoolSnapshotTaskCreateResult,
audit='Snapshot task create:',
audit_extended=lambda data: data['dataset']
)
Expand Down Expand Up @@ -158,16 +144,11 @@ async def do_create(self, data):

return await self.get_instance(data['id'])

@accepts(
Int('id', required=True),
Patch(
'periodic_snapshot_create',
'periodic_snapshot_update',
('add', {'name': 'fixate_removal_date', 'type': 'bool'}),
('attr', {'update': True})
),
@api_method(
PoolSnapshotTaskUpdateArgs,
PoolSnapshotTaskUpdateResult,
audit='Snapshot task update:',
audit_callback=True,
audit_callback=True
)
async def do_update(self, audit_callback, id_, data):
"""
Expand Down Expand Up @@ -255,14 +236,11 @@ async def do_update(self, audit_callback, id_, data):

return await self.get_instance(id_)

@accepts(
Int('id'),
Dict(
'options',
Bool('fixate_removal_date', default=False),
),
@api_method(
PoolSnapshotTaskDeleteArgs,
PoolSnapshotTaskDeleteResult,
audit='Snapshot task delete:',
audit_callback=True,
audit_callback=True
)
async def do_delete(self, audit_callback, id_, options):
"""
Expand Down Expand Up @@ -318,8 +296,7 @@ async def do_delete(self, audit_callback, id_, options):

return response

@accepts()
@returns(Int())
@api_method(PoolSnapshotTaskMaxCountArgs, PoolSnapshotTaskMaxCountResult)
def max_count(self):
"""
Returns a maximum amount of snapshots (per-dataset) the system can sustain.
Expand All @@ -329,8 +306,7 @@ def max_count(self):
# with too many, then File Explorer will show no snapshots available.
return 512

@accepts()
@returns(Int())
@api_method(PoolSnapshotTaskMaxTotalCountArgs, PoolSnapshotTaskMaxTotalCountResult)
def max_total_count(self):
"""
Returns a maximum amount of snapshots (total) the system can sustain.
Expand All @@ -341,7 +317,7 @@ def max_total_count(self):
return 10000

@item_method
@accepts(Int("id"))
@api_method(PoolSnapshotTaskRunArgs, PoolSnapshotTaskRunResult)
async def run(self, id_):
"""
Execute a Periodic Snapshot Task of `id`.
Expand Down
2 changes: 0 additions & 2 deletions src/middlewared/middlewared/plugins/snapshot_/removal_date.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import hashlib

from middlewared.service import Service, job, private


Expand Down
Loading

0 comments on commit 8af0dc7

Please sign in to comment.