Skip to content

Commit d7b8769

Browse files
authored
NAS-137223 / 26.04 / remove most internal calls to zfs.snapshot.query (#17022)
This removes most internal calls to `zfs.snapshot.query` since we can now gather this information with our newly minted `zfs.resource.query_impl` endpoint. The performance/efficiency differences are extreme (in the positive).
1 parent fc2b6c2 commit d7b8769

File tree

9 files changed

+63
-26
lines changed

9 files changed

+63
-26
lines changed

src/middlewared/middlewared/plugins/apps/rollback.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ def rollback(self, job, app_name, options):
7272
app_volume_ds := self.middleware.call_sync('app.get_app_volume_ds', app_name)
7373
):
7474
snap_name = f'{app_volume_ds}@{options["app_version"]}'
75-
if self.middleware.call_sync('zfs.snapshot.query', [['id', '=', snap_name]]):
75+
if self.middleware.call_sync('zfs.resource.snapshot_exists', snap_name):
7676
job.set_progress(40, f'Rolling back {app_name!r} app to {options["app_version"]!r} version')
77-
7877
self.middleware.call_sync(
7978
'zfs.snapshot.rollback', snap_name, {
8079
'force': True,

src/middlewared/middlewared/plugins/apps/upgrade.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def take_snapshot_of_hostpath_and_stop_app(self, app, snapshot_hostpath):
6060
continue
6161

6262
snap_name = f'{dataset}@{get_upgrade_snap_name(app_info["name"], app_info["version"])}'
63-
if self.middleware.call_sync('zfs.snapshot.query', [['id', '=', snap_name]]):
63+
if self.middleware.call_sync('zfs.resource.snapshot_exists', snap_name):
6464
logger.debug('Snapshot %r already exists for %r app', snap_name, app_info['name'])
6565
continue
6666

@@ -139,7 +139,7 @@ def upgrade(self, job, app_name, options):
139139

140140
if app_volume_ds := self.middleware.call_sync('app.get_app_volume_ds', app_name):
141141
snap_name = f'{app_volume_ds}@{app["version"]}'
142-
if self.middleware.call_sync('zfs.snapshot.query', [['id', '=', snap_name]]):
142+
if self.middleware.call_sync('zfs.resource.snapshot_exists', snap_name):
143143
self.middleware.call_sync('zfs.snapshot.delete', snap_name, {'recursive': True})
144144

145145
self.middleware.call_sync(

src/middlewared/middlewared/plugins/docker/backup.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import datetime
12
import errno
23
import logging
34
import os
45
import shutil
6+
57
import yaml
6-
from datetime import datetime
78

89
from middlewared.api import api_method
910
from middlewared.api.current import (
@@ -42,14 +43,14 @@ def backup(self, job, backup_name):
4243
"""
4344
self.middleware.call_sync('docker.state.validate')
4445
docker_config = self.middleware.call_sync('docker.config')
45-
name = backup_name or datetime.now().strftime('%F_%T')
46+
name = backup_name or datetime.datetime.now().strftime('%F_%T')
4647
if not validate_snapshot_name(f'a@{name}'):
4748
# The a@ added is just cosmetic as the function requires a complete snapshot name
4849
# with the dataset name included in it
4950
raise CallError(f'{name!r} is not a valid snapshot name. It should be a valid ZFS snapshot name')
5051

5152
snap_name = BACKUP_NAME_PREFIX + name
52-
if self.middleware.call_sync('zfs.snapshot.query', [['id', '=', f'{docker_config["dataset"]}@{snap_name}']]):
53+
if self.middleware.call_sync('zfs.resource.snapshot_exists', snap_name):
5354
raise CallError(f'{snap_name!r} snapshot already exists', errno=errno.EEXIST)
5455

5556
if name in self.list_backups():
@@ -95,13 +96,21 @@ def list_backups(self):
9596

9697
backups_base_dir = backup_ds_path()
9798
backups = {}
98-
snapshots = self.middleware.call_sync(
99-
'zfs.snapshot.query', [
100-
['name', '^', f'{docker_config["dataset"]}@{BACKUP_NAME_PREFIX}']
101-
], {'select': ['name']}
99+
ds = self.middleware.call_sync(
100+
'zfs.resource.query_impl',
101+
{'paths': [docker_config['dataset']], 'properties': None, 'get_snapshots': True}
102102
)
103-
for snapshot in snapshots:
104-
backup_name = snapshot['name'].split('@', 1)[-1].split(BACKUP_NAME_PREFIX, 1)[-1]
103+
if not ds:
104+
return backups
105+
elif not ds[0]["snapshots"]:
106+
return backups
107+
108+
prefix = f'{docker_config["dataset"]}@{BACKUP_NAME_PREFIX}'
109+
for snap_name, snap_info in ds[0]['snapshots'].items():
110+
if not snap_name.startswith(prefix):
111+
continue
112+
113+
backup_name = snap_name.split('@', 1)[-1].split(BACKUP_NAME_PREFIX, 1)[-1]
105114
backup_path = os.path.join(backups_base_dir, backup_name)
106115
if not os.path.exists(backup_path):
107116
continue
@@ -115,10 +124,13 @@ def list_backups(self):
115124
backups[backup_name] = {
116125
'name': backup_name,
117126
'apps': [{k: app[k] for k in ('id', 'name', 'state')} for app in apps.values()],
118-
'snapshot_name': snapshot['name'],
119-
'created_on': str(self.middleware.call_sync(
120-
'zfs.snapshot.get_instance', snapshot['name']
121-
)['properties']['creation']['parsed']),
127+
'snapshot_name': snap_name,
128+
'created_on': str(
129+
datetime.datetime.fromtimestamp(
130+
snap_info["properties"]["creation"]["value"],
131+
datetime.UTC
132+
)
133+
),
122134
'backup_path': backup_path,
123135
}
124136

@@ -168,7 +180,7 @@ async def post_system_update_hook(middleware):
168180
break
169181

170182
backup_job = await middleware.call(
171-
'docker.backup', f'{UPDATE_BACKUP_PREFIX}-{datetime.now().strftime("%F_%T")}'
183+
'docker.backup', f'{UPDATE_BACKUP_PREFIX}-{datetime.datetime.now().strftime("%F_%T")}'
172184
)
173185
await backup_job.wait()
174186
if backup_job.error:

src/middlewared/middlewared/plugins/docker/migrate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,5 @@ async def replicate_apps_dataset(self, new_pool, old_pool, migration_options):
8484
finally:
8585
await self.middleware.call('zfs.snapshot.delete', snap_details['id'], {'recursive': True})
8686
snap_name = f'{applications_ds_name(new_pool)}@{snap_details["snapshot_name"]}'
87-
if await self.middleware.call('zfs.snapshot.query', [['id', '=', snap_name]]):
87+
if await self.middleware.call('zfs.resource.snapshot_exists', snap_name):
8888
await self.middleware.call('zfs.snapshot.delete', snap_name, {'recursive': True})

src/middlewared/middlewared/plugins/vm/clone.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,18 @@ async def __next_clone_name(self, name):
3131
return clone_name
3232

3333
async def __clone_zvol(self, name, zvol, created_snaps, created_clones):
34-
if not await self.middleware.call(
35-
'zfs.resource.query_impl', {'paths': [zvol], 'properties': None}
36-
):
34+
zz = await self.middleware.call(
35+
'zfs.resource.query_impl',
36+
{'paths': [zvol], 'properties': None, 'get_snapshots': True}
37+
)
38+
if not zz:
3739
raise CallError(f'zvol {zvol} does not exist.', errno.ENOENT)
3840

3941
snapshot_name = name
4042
i = 0
4143
while True:
4244
zvol_snapshot = f'{zvol}@{snapshot_name}'
43-
if await self.middleware.call('zfs.snapshot.query', [('id', '=', zvol_snapshot)]):
45+
if zvol_snapshot in zz[0]['snapshots']:
4446
if ZVOL_CLONE_RE.search(snapshot_name):
4547
snapshot_name = ZVOL_CLONE_RE.sub(rf'\1{ZVOL_CLONE_SUFFIX}{i}', snapshot_name)
4648
else:

src/middlewared/middlewared/plugins/zfs/resource_crud.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,18 @@ def query_impl(self, tls, data: dict | None = None):
129129
else:
130130
return results
131131

132+
@private
133+
def snapshot_exists(self, snap_name: str):
134+
"""Check to see if a given snapshot exists.
135+
NOTE: internal method so lots of assumptions
136+
are made by the passed in `snap_name` arg."""
137+
ds, snap_name = snap_name.split("@")
138+
rv = self.middleware.call_sync(
139+
"zfs.resource.query_impl",
140+
{"paths": [ds], "properties": None, "get_snapshots": True}
141+
)
142+
return rv and snap_name in rv[0]["snapshots"]
143+
132144
@api_method(
133145
ZFSResourceQueryArgs,
134146
ZFSResourceQueryResult,

tests/api2/test_account_privilege_role.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ def test_can_read_with_read_or_write_role(role):
1717
with dataset("test_snapshot_read") as ds:
1818
with snapshot(ds, "test"):
1919
with unprivileged_user_client([role]) as c:
20-
assert len(c.call("pool.snapshot.query", [["dataset", "=", ds]])) == 1
20+
snaps = c.call(
21+
"zfs.resource.query",
22+
{"paths": [ds], "properties": None, "get_snapshots": True}
23+
)[0]["snapshots"]
24+
assert len(snaps) == 1
2125

2226

2327
def test_can_not_write_with_read_role():

tests/api2/test_cloud_backup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,11 @@ def test_snapshot(s3_credential):
451451
]) == ["blob", "blob2"]
452452

453453
time.sleep(1)
454-
assert call("pool.snapshot.query", [["dataset", "=", ds]]) == []
454+
snaps = call(
455+
"zfs.resource.query",
456+
{"paths": [ds], "properties": None, "get_snapshots": True}
457+
)[0]["snapshots"]
458+
assert len(snaps) == 0
455459

456460

457461
@pytest.mark.parametrize("cloud_backup_task, expected", [(

tests/api2/test_cloud_sync.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@ def test_snapshot(has_zvol_sibling):
120120

121121
time.sleep(1)
122122

123-
assert call("pool.snapshot.query", [["dataset", "=", ds]]) == []
123+
snaps = call(
124+
"zfs.resource.query",
125+
{"paths": [ds], "properties": None, "get_snapshots": True}
126+
)[0]["snapshots"]
127+
assert len(snaps) == 0
124128
finally:
125129
if has_zvol_sibling:
126130
ssh(f"zfs destroy -r {pool}/zvol")

0 commit comments

Comments
 (0)