Skip to content

Commit 037d94d

Browse files
General updates
1 parent 40162b2 commit 037d94d

6 files changed

Lines changed: 89 additions & 72 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ See examples folder
2929
3030
# Load poolset.
3131
# Properties can be queried here with: properties=['prop1','prop2',...]
32-
# Default properties: name, creation, mountpoint
32+
# Default properties: name, creation
33+
# If get_mounts=True, mountpoint and mounted are also retrieved automatically
34+
# unlocking some functionality
35+
# To see all available properties use: % zfs list -o foo
3336
poolset = conn.load_poolset()
3437
3538
# Load a pool by name
@@ -44,6 +47,7 @@ See examples folder
4447
# <dataset>.mountpoint -> str
4548
# <dataset|snapshot>.pool -> Pool
4649
# <snapshot>.dataset -> DataSet
50+
# <dataset>.dspath -> str: Dataset path excluding pool name
4751
# <ZFSItem>.parent -> ZfsItem
4852
# <pool|dataset>.children -> list(of ZfsItem)
4953
# . Pools only contain DataSets

examples/ex_local.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def main(argv):
2424
p = poolset.get_pool('dpool')
2525
ds = p.get_dataset('vcmain')
2626
all_snaps = ds.get_all_snapshots()
27+
2728
if len(all_snaps) == 0:
2829
print('No snapshots found for dataset: {}'.format(ds))
2930
else:

examples/ex_other.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ def main(argv):
1313
conn = zfs.Connection(host='localhost')
1414

1515
# Load poolset
16-
# Note: the following properties are automatically
17-
# retrieved: 'name','creation','used','available','referenced','mountpoint'
16+
# Note: the properties name and creation are automatically retrieved
17+
# If get_mounts=True, mountpoint and mounted are also retrieved automatically
1818
# To see all available properties use: % zfs list -o foo
19-
poolset = conn.load_poolset(properties=["avail", "usedsnap", "usedrefreserv", "usedchild"])
19+
poolset = conn.load_poolset(get_mounts=True, properties=["avail", "usedsnap", "usedrefreserv", "usedchild"])
2020

2121

2222
# Print all datasets test
@@ -41,6 +41,7 @@ def main(argv):
4141
# Get Snapshot path
4242
path_to_find = '/dpool/vcmain/py/zfs'
4343
for i, snap in enumerate(snapshots):
44+
print(snap.snap_path)
4445
if i > 0:
4546
diffs = ds.get_diffs(snap_last, snap, file_type='F', chg_type='M', include=['*.py', '*.js'], exclude=['*.vscod*', '*_pycache_*'])
4647
if len(diffs) > 0:

examples/zfslib_ex_common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
def print_all_datasets(pool: zfs.Pool):
77
allds = pool.get_all_datasets(with_depth=True)
88
for (depth, ds) in allds:
9-
print("{}: {} {} ({}) - [{}]".format(pool.name, ' .'*depth, ds.name, ds.name, ds.get_property('mountpoint')))
9+
print("{}: {} {} ({})".format(pool.name, ' .'*depth, ds.name, ds.dspath))
1010

1111

1212

1313
def print_all_datasets(pool: zfs.Pool):
1414
allds = pool.get_all_datasets(with_depth=True)
1515
for (depth, ds) in allds:
16-
print("{}: {} {} ({}) - [{}]".format(pool.name, ' .'*depth, ds.name, ds.name, ds.get_property('mountpoint')))
16+
print("{}: {} {} ({})".format(pool.name, ' .'*depth, ds.name, ds.dspath))
1717

1818

1919
# This can be very slow for large datasets. Its actually `zfs diff` thats the slow part

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from setuptools import setup
55
import os
66

7-
VERSION = "0.5"
7+
VERSION = "0.6"
88

99

1010
def get_long_description():

zfslib/zfslib.py

Lines changed: 76 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ def extract_properties(s):
172172
# traverse the child hierarchy or create if that fails
173173
try: fs = fs.get_child(pcomp)
174174
except KeyError:
175-
fs = Dataset(pcomp, pool_cur, fs)
175+
fs = Dataset(pool_cur, pcomp, fs)
176176

177177
if snapshot:
178178
if snapshot not in [ x.name for x in fs.children ]:
179-
fs = Snapshot(snapshot, fs)
179+
fs = Snapshot(pool_cur, snapshot, fs)
180180

181181
fs._properties.update( creations[fs.path] )
182182

@@ -214,7 +214,8 @@ def walk(self):
214214
yield dset
215215

216216
def __iter__(self):
217-
return self.walk()
217+
for pool in self._pools:
218+
yield self._pools[pool]
218219

219220

220221
# Wrappers for testing
@@ -239,7 +240,8 @@ class ZFSItem(object):
239240

240241
creation = property(lambda self: datetime.fromtimestamp(int(self._properties["creation"])))
241242

242-
def __init__(self, name, parent=None):
243+
def __init__(self, pool, name, parent=None):
244+
self.pool = pool
243245
self.name = name
244246
self.children = []
245247
self._properties = {}
@@ -265,9 +267,6 @@ def remove(self, child):
265267
for c in child.children:
266268
child.remove(c)
267269

268-
def get_relative_name(self):
269-
if not self.parent: return self.name
270-
return self.path[len(self.parent.path) + 1:]
271270

272271
def walk(self):
273272
assert not self.invalidated, "%s invalidated" % self
@@ -286,30 +285,13 @@ def has_property(self, name):
286285
return (name in self._properties)
287286

288287

289-
# For name, use full dataset path
290-
def get_dataset(self, name):
291-
assert not(isinstance(self, Snapshot)), "get_dataset(name) cannot be used on Snapshot Objects. Use Snapshot.dataset instead."
292-
allds = self.get_all_datasets()
293-
pool_name = self.name if isinstance(self, Pool) else self.pool.name
294-
nfind = name if name.find(pool_name+'/') == 0 else '{}/{}'.format(pool_name, name)
295-
for dataset in allds:
296-
if dataset.path == nfind:
297-
return dataset
298-
raise ValueError("Dataset '{}' not found in pool '{}'.".format(name, self.pool.name))
299-
300-
301-
# returns list(of str) or if with_depth == True then list(of tuple(of depth, Dataset))
302-
def get_all_datasets(self, with_depth:bool=False, depth:int=0):
303-
a = []
304-
for c in self.children:
305-
if isinstance(c, Dataset):
306-
a.append( (depth, c) if with_depth else c )
307-
a = a + c.get_all_datasets(with_depth=with_depth, depth=depth+1)
308-
return a
309288

310289

311290
class Snapable(ZFSItem): # Abstract class for Pools and Datasets
312291

292+
def __init__(self, pool, name, parent=None):
293+
super().__init__(pool, name, parent)
294+
313295
# Lookup for Datasets or Snapshot by dataset relative path
314296
# Eg. for snapshots: <dataset_path>@<snapshot>
315297
def lookup(self, name):
@@ -353,6 +335,7 @@ def get_snapshots(self, flt=True, index=False):
353335
for idx, c in enumerate(self.children):
354336
if isinstance(c, Snapshot) and flt(c):
355337
res.append( (idx, c, self.path) if index else c )
338+
356339
return res
357340

358341

@@ -438,9 +421,57 @@ def __fil_dt(snap):
438421
else:
439422
(dt_f, dt_t) = calcDateRange(tdelta=tdelta, dt_from=dt_from, dt_to=dt_to)
440423

424+
441425
return self.get_snapshots(flt=f, index=index)
442426

443427

428+
429+
430+
431+
# For name, use full dataset path
432+
def get_dataset(self, name):
433+
assert not(isinstance(self, Snapshot)), "get_dataset(name) cannot be used on Snapshot Objects. Use Snapshot.dataset instead."
434+
allds = self.get_all_datasets()
435+
pool_name = self.name if isinstance(self, Pool) else self.pool.name
436+
nfind = name if name.find(pool_name+'/') == 0 else '{}/{}'.format(pool_name, name)
437+
for dataset in allds:
438+
if dataset.path == nfind:
439+
return dataset
440+
raise ValueError("Dataset '{}' not found in pool '{}'.".format(name, self.pool.name))
441+
442+
443+
# returns list(of str) or if with_depth == True then list(of tuple(of depth, Dataset))
444+
def get_all_datasets(self, with_depth:bool=False, depth:int=0):
445+
a = []
446+
for c in self.children:
447+
if isinstance(c, Dataset):
448+
a.append( (depth, c) if with_depth else c )
449+
a = a + c.get_all_datasets(with_depth=with_depth, depth=depth+1)
450+
return a
451+
452+
453+
class Pool(Snapable):
454+
def __init__(self, name, conn:Connection, have_mounts:bool):
455+
super().__init__(self, name)
456+
self.connection = conn
457+
self.have_mounts = have_mounts
458+
459+
460+
def __str__(self):
461+
return "<Pool: %s>" % self.path
462+
463+
__repr__ = __str__
464+
465+
466+
467+
class Dataset(Snapable):
468+
469+
def __init__(self, pool, name, parent=None):
470+
super().__init__(pool, name, parent)
471+
self.dspath = self.path[len(pool.name)+1:]
472+
self._mountpoint=None
473+
474+
444475
# get_diffs() - Gets Diffs in snapshot or between snapshots (if snap_to is specified)
445476
# snap_from - Left side of diff
446477
# snap_to - Right side of diff. If not specified, diff is to working copy
@@ -524,31 +555,6 @@ def __row(s):
524555
return diffs
525556

526557

527-
528-
class Pool(Snapable):
529-
def __init__(self, name, conn:Connection, have_mounts:bool):
530-
self.name = name
531-
self.children = []
532-
self._properties = {}
533-
self.connection = conn
534-
self.have_mounts = have_mounts
535-
536-
537-
def __str__(self):
538-
return "<Pool: %s>" % self.path
539-
540-
__repr__ = __str__
541-
542-
543-
544-
class Dataset(Snapable):
545-
546-
def __init__(self, name, pool:Pool, parent=None):
547-
super().__init__(name, parent)
548-
self._mountpoint=None
549-
self.pool=pool
550-
self.dspath = self.path[len(pool.name)+1:]
551-
552558
def _get_mountpoint(self):
553559
if self._mountpoint is None:
554560
self.assertHaveMounts()
@@ -569,14 +575,20 @@ def get_rel_path(self, path) -> str:
569575
p_real = os.path.realpath(p_real)
570576
mp = self.mountpoint
571577
if not p_real.find(mp) == 0:
572-
raise KeyError('path given is not in current datastore mountpoint {}. Path: {}'.format(mp, path))
578+
raise KeyError('path given is not in current dataset mountpoint {}. Path: {}'.format(mp, path))
573579
return p_real.replace(mp, '')
574-
580+
575581

576582
def assertHaveMounts(self):
577583
assert self.pool.have_mounts, "Mount information not loaded. Please use Connection.load_poolset(get_mounts=True)."
578584

579585

586+
587+
def get_relative_name(self):
588+
if not self.parent: return self.name
589+
return self.path[len(self.parent.path) + 1:]
590+
591+
580592
def __str__(self):
581593
if self.pool.have_mounts:
582594
return "<Dataset: %s> mountpoint: %s" % (self.path, self.mountpoint)
@@ -589,24 +601,23 @@ def __str__(self):
589601

590602

591603

592-
593-
594604
class Snapshot(ZFSItem):
595605

596-
# def _get_dataset(self): return self.parent
597-
dataset = property(lambda self: self.parent)
606+
def __init__(self, pool, name, parent=None):
607+
super().__init__(pool, name, parent)
608+
self.dataset = parent if isinstance(parent, Dataset) else None
598609

599-
pool = property(lambda self: self.parent.pool)
600610

601611
def _get_path(self):
602612
if not self.parent: return self.name
603613
return "%s@%s" % (self.parent.path, self.name)
604614
path = property(_get_path)
605615

616+
606617
# Resolves the path to .zfs/snapshot directory
607618
def get_snap_path(self):
608-
self.dataset.assertHaveMounts()
609-
return "{}/.zfs/snapshot/{}".format(self.dataset.mountpoint, self.name)
619+
self.parent.assertHaveMounts()
620+
return "{}/.zfs/snapshot/{}".format(self.parent.mountpoint, self.name)
610621
snap_path = property(get_snap_path)
611622

612623

@@ -618,7 +629,7 @@ def get_snap_path(self):
618629
# - str = Path to item if found else path to .zfs/snapshot directory
619630
# eg: (found, rel_path) = snap.resolve_snap_path('<some_path_on_system>')
620631
def resolve_snap_path(self, path):
621-
self.dataset.assertHaveMounts()
632+
self.parent.assertHaveMounts()
622633
if path is None or not isinstance(path, str) or path.strip() == '':
623634
assert 0, "path must be a non-blank string"
624635
path = os.path.abspath( pathlib.Path(path).expanduser() )
@@ -734,9 +745,9 @@ def __str__(self):
734745

735746

736747

737-
# Will resolve Pool and Datastore for a path on local filesystem using the mountpoint
738-
# returns (Pool, DataStore, Real_Path, Relative_Path)
739-
def find_datastore_for_path(poolset:PoolSet, path:str) -> tuple:
748+
# Will resolve Pool and Dataset for a path on local filesystem using the mountpoint
749+
# returns (Pool, Dataset, Real_Path, Relative_Path)
750+
def find_dataset_for_path(poolset:PoolSet, path:str) -> tuple:
740751
assert poolset.have_mounts, "Mount information not loaded. Please use Connection.load_poolset(get_mounts=True)."
741752
p_real = os.path.abspath( pathlib.Path(path).expanduser() )
742753
p_real = os.path.realpath(p_real)

0 commit comments

Comments
 (0)