5
5
# Author: Timothy C. Quinn
6
6
# Home: https://github.com/JavaScriptDude/zfslib
7
7
# Licence: https://opensource.org/licenses/BSD-3-Clause
8
+ # TODO:
9
+ # [.] Allow querying of just zpool properties only rather than digging all zfs list -t all for every call
10
+ # - This will make it much faster for such queries
8
11
#########################################
9
12
10
13
import subprocess
18
21
19
22
is_py2 = (sys .version_info [0 ] == 2 )
20
23
24
+ class __DEFAULT__ (object ):pass
25
+
21
26
class Connection :
22
27
host = None
23
28
_poolset = None
@@ -47,24 +52,16 @@ def __init__(self, host="localhost", trust=False, sshcipher=None, identityfile=N
47
52
self .command .extend ([self .host ])
48
53
49
54
50
- # Data is cached unless force=True, snapshots have been made since last read
51
- # or properties is different
52
- def load_poolset (self , properties = None , get_mounts = True , force = False , _test_data = None ):
53
- properties = [] if properties is None else properties
54
- if force or self ._dirty or not self ._props_last == properties :
55
- self ._poolset ._load (properties = properties , get_mounts = get_mounts , _test_data = _test_data )
56
- self ._dirty = False
57
- self ._props_last = properties
58
-
59
- return self ._poolset
60
-
55
+
56
+ # See PoolSet._load for parameters
61
57
62
- def snapshot_recursively (self , name , snapshotname , properties = None ):
63
- properties = {} if properties is None else properties
64
- plist = sum ( map ( lambda x : [ '-o' , '%s=%s' % x ], properties . items () ), [] )
65
- subprocess . check_call ( self .command + [ "zfs" , "snapshot" , "-r" ] + plist + [ "%s@%s" % ( name , snapshotname )] )
66
- self ._dirty = True
58
+ def load_poolset (self , zfs_props = None , zpool_props = None , get_mounts = True , force = False , _test_data_zfs = None , _test_data_zpool = None ):
59
+ zfs_props = [] if zfs_props is None else zfs_props
60
+ if force or not self . _props_last == zfs_props :
61
+ self ._poolset . _load ( zfs_props = zfs_props , zpool_props = zpool_props , get_mounts = get_mounts , _test_data_zfs = _test_data_zfs , _test_data_zpool = _test_data_zpool )
62
+ self ._props_last = zfs_props
67
63
64
+ return self ._poolset
68
65
69
66
70
67
class PoolSet (object ):
@@ -105,67 +102,96 @@ def lookup(self, name):
105
102
106
103
return ret
107
104
108
- # Note: _test_data is for testing only
109
- # get_mounts will automated grabbing of mountpoint and mounted properties and
110
- # store flag for downstream code to know that these flags are available
111
- def _load (self , get_mounts = True , properties = None , _test_data = None ):
105
+ # [zfs_props] properties from % zfs list -o <properties>
106
+ # [zpool_props] properties from % zpool list -o <properties>
107
+ # [get_mounts] Append mountpoint and mounted zfs_props and store flag for downstream code to know that these flags are available
108
+ # [_test_data_zfs] testing only
109
+ # [_test_data_zpool] testing only
110
+ def _load (self , get_mounts = True , zfs_props = None , zpool_props = None , _test_data_zfs = None , _test_data_zpool = None ):
112
111
global is_py2
113
112
114
- _pdef = ['name' , 'creation' ]
113
+ # setup zfs list properties (zfs list -o <props>)
114
+ _zfs_pdef = ['name' , 'creation' ]
115
115
116
- if properties is None :
116
+ if zfs_props is None :
117
117
if get_mounts :
118
- _pdef .extend (['mountpoint' , 'mounted' ])
118
+ _zfs_pdef .extend (['mountpoint' , 'mounted' ])
119
119
self .have_mounts = True
120
- properties = _pdef
120
+ zfs_props = _zfs_pdef
121
121
122
122
else :
123
- if 'mountpoint' in properties and 'mounted' in properties :
123
+ if 'mountpoint' in zfs_props and 'mounted' in zfs_props :
124
124
self .have_mounts = True
125
125
126
126
elif get_mounts :
127
- _pdef .extend (['mountpoint' , 'mounted' ])
127
+ _zfs_pdef .extend (['mountpoint' , 'mounted' ])
128
128
self .have_mounts = True
129
129
130
130
else :
131
131
self .have_mounts = False
132
132
133
- properties = _pdef + [s for s in properties if not s in _pdef ]
133
+ zfs_props = _zfs_pdef + [s for s in zfs_props if not s in _zfs_pdef ]
134
+
135
+
136
+
137
+ # setup zpool list properties (zpool list -o <props>)
138
+ _zpool_pdef = ['name' , 'size' , 'allocated' , 'free' , 'checkpoint' , 'fragmentation' , 'capacity' , 'health' ]
139
+ if zpool_props is None :
140
+ zpool_props = _zpool_pdef
141
+ else :
142
+ zpool_props = _zpool_pdef + [s for s in zpool_props if not s in _zpool_pdef ]
143
+
134
144
135
145
_base_cmd = self .connection .command
136
146
137
- def extract_properties (s ):
147
+ def extract_properties (s , zpool :bool = False ):
148
+ props = zpool_props if zpool else zfs_props
138
149
if not is_py2 and isinstance (s , bytes ): s = s .decode ('utf-8' )
139
150
items = s .strip ().split ( '\t ' )
140
- assert len ( items ) == len ( properties ), (properties , items )
141
- propvalues = map ( lambda x : None if x == '-' else x , items [ 1 : ] )
142
- return [ items [ 0 ], zip ( properties [ 1 : ], propvalues ) ]
151
+ assert len ( items ) == len ( props ), (props , items )
152
+ propvalues = map ( lambda x : None if x == '-' else x , items [1 :] )
153
+ return [ items [ 0 ], zip ( props [ 1 : ], propvalues ) ]
154
+
155
+ # Gather zfs list data
156
+ if _test_data_zfs is None :
157
+ zfs_list_output = subprocess .check_output (self .connection .command + ["zfs" , "list" , "-Hpr" , "-o" , "," .join ( zfs_props ), "-t" , "all" ])
158
+
159
+ else : # Use test data
160
+ zfs_list_output = _test_data_zfs
161
+
162
+ zfs_list_items = OrderedDict ([ extract_properties (s ) for s in zfs_list_output .splitlines () if s .strip () ])
143
163
144
- if _test_data is None :
145
- zfs_list_output = subprocess .check_output (self .connection .command + ["zfs" , "list" , "-Hpr" , "-o" , "," .join ( properties ), "-t" , "all" ])
164
+
165
+ # Gather zpool list data
166
+ if _test_data_zpool is None :
167
+ zpool_list_output = subprocess .check_output (self .connection .command + ["zpool" , "list" , "-Hp" , "-o" , "," .join ( zpool_props )])
146
168
147
169
else : # Use test data
148
- zfs_list_output = _test_data
170
+ zpool_list_output = _test_data_zpool
171
+
172
+ zpool_list_items = OrderedDict ([ extract_properties (s , zpool = True ) for s in zpool_list_output .splitlines () if s .strip () ])
149
173
150
- creations = OrderedDict ([ extract_properties ( s ) for s in zfs_list_output .splitlines () if s .strip () ])
151
174
152
175
# names of pools
153
- old_dsets = [ x .path for x in self .walk () ]
154
- old_dsets .reverse ()
155
- new_dsets = creations .keys ()
176
+ old_items = [ x .path for x in self .walk () ]
177
+ old_items .reverse ()
178
+ new_items = zfs_list_items .keys ()
156
179
pool_cur = None
157
180
158
- for dset in new_dsets :
159
- if "@" in dset :
160
- dset , snapshot = dset .split ("@" )
181
+ for name in new_items :
182
+ is_zpool = False
183
+ if "@" in name :
184
+ name , snapshot = name .split ("@" )
161
185
else :
162
186
snapshot = None
163
- if "/" not in dset : # pool name
164
- if dset not in self ._pools :
165
- pool_cur = Pool (dset , self .connection , self .have_mounts )
166
- self ._pools [dset ] = pool_cur
167
- fs = self ._pools [dset ]
168
- poolname , pathcomponents = dset .split ("/" )[0 ], dset .split ("/" )[1 :]
187
+ if "/" not in name : # zpool
188
+ if name not in self ._pools :
189
+ is_zpool = True
190
+ pool_cur = Pool (name , self .connection , self .have_mounts )
191
+ self ._pools [name ] = pool_cur
192
+ fs = self ._pools [name ]
193
+
194
+ poolname , pathcomponents = name .split ("/" )[0 ], name .split ("/" )[1 :]
169
195
fs = self ._pools [poolname ]
170
196
for pcomp in pathcomponents :
171
197
# traverse the child hierarchy or create if that fails
@@ -177,17 +203,25 @@ def extract_properties(s):
177
203
if snapshot not in [ x .name for x in fs .children ]:
178
204
fs = Snapshot (pool_cur , snapshot , fs )
179
205
180
- fs ._properties .update ( creations [fs .path ] )
206
+ fs ._properties .update ( zfs_list_items [fs .path ] )
207
+
208
+ if is_zpool :
209
+ # Update with zpool properties
210
+ _zpool_props = zpool_list_items .get (name , __DEFAULT__ )
211
+ assert _zpool_props != __DEFAULT__ , f"ERROR - zpool '{ name } ' not found in zpool_list_items"
212
+ fs ._properties .update ( _zpool_props )
213
+
214
+ noop ()
181
215
182
216
# std_avail is avail, std_ref is usedds
183
217
184
218
185
- for dset in old_dsets :
186
- if dset not in new_dsets :
187
- if "/" not in dset and "@" not in dset : # a pool
188
- self .remove (dset )
219
+ for name in old_items :
220
+ if name not in new_items :
221
+ if "/" not in name and "@" not in name : # a pool
222
+ self .remove (name )
189
223
else :
190
- d = self .lookup (dset )
224
+ d = self .lookup (name )
191
225
d .parent .remove (d )
192
226
193
227
@@ -1031,3 +1065,7 @@ def f(*popenargs, **kwargs):
1031
1065
1032
1066
1033
1067
''' END LEGACY DUCK PUNCHING '''
1068
+
1069
+ # No operation lambda dropin or breakpoint marker
1070
+ def noop (* args , ** kwargs ):
1071
+ if len (args ): return args [0 ]
0 commit comments