Newer
Older

Chad Kerner
committed
#=====================================================================================
# Chad Kerner, Systems Engineer
# Storage Enabling Technologies
# National Center for Supercomputing Applications
# ckerner@illinois.edu chad.kerner@gmail.com
#=====================================================================================

Chad Kerner
committed
# This was born out of a need to programmatically interface with IBM Spectrum
# Scale or the software formerly knows as GPFS.

Chad Kerner
committed
# This was written in Python 2.7 with Spectrum Scale 4.2. That is what we currently
# have and so that is what I will keep it updated with until other needs arise.

Chad Kerner
committed
# There is NO support, use it at your own risk. Although I have not coded anything
# too awfully dramatic in here.
#
# If you find a bug, fix it. Then send me the diff and I will merge it into the code.

Chad Kerner
committed
# You may want to pull often because this is being updated quite frequently as our
# needs arise in our clusters.
#
#=====================================================================================
from __future__ import print_function
from subprocess import Popen, PIPE
import sys
import shlex
def run_cmd( cmdstr=None ):
"""
Wrapper around subprocess module calls.
"""
if not cmdstr:
return None
cmd = shlex.split(cmdstr)
subp = Popen(cmd, stdout=PIPE, stderr=PIPE)
(outdata, errdata) = subp.communicate()
if subp.returncode != 0:

Chad Kerner
committed
msg = "Error\n Command: {0}\n Message: {1}".format(cmdstr,errdata)

Chad Kerner
committed
sys.exit( subp.returncode )
"""
The mmlsfileset command returns encoded strings for special characters.
This will replace those encoded strings and return a true string.
"""
tempstring = mystring.replace('%2F', '/')
mystring = tempstring
tempstring = mystring.replace('%3A', ':')
return tempstring
def remove_special_characters( mystring ):
The mmlspool command has returns strings with special characters.
This will remove those encoded strings.
"""
tempstring = mystring.replace('%', '')
mystring = tempstring
tempstring = mystring.replace('(', '')
mystring = tempstring
tempstring = mystring.replace(')', '')
return tempstring
class Nsds:
"""
This class contains all of the information about the NSDs in the cluster. It
includes the NSD name, servers they are attached to, gpfs device they serve, etc.
gpfsdevs - a list unique gpfs devices
nsds[name]['usage'] = The gpfs device the specified name is a part of
nsds[name]['servers'] = The storage servers the specified name is hosted by
def dump( self ):
print("GPFS Devices: {}".format(self.gpfsdevs))
nsd_keys = self.nsds.keys()
for nsd in sorted(nsd_keys):
print("{:<10s} {:<10s} {:<s}".format(nsd, self.nsds[nsd]['usage'], self.nsds[nsd]['servers']))
def return_gpfs_devices( self ):
"""
Return an iterable list of uniq GPFS devices.
"""
return self.gpfsdevs
def collect_nsd_info( self ):
Process the mmlsnsd command output to build the necessary structures.
fsdevs = {}
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmlsnsd")
for line in cmd_out.splitlines():
line.rstrip()
# Ignore blank lines
if not line:
continue
# Ignore dashed lines
if '----------' in line:
continue
# Ignore header lines
if 'File system' in line:
continue
if '(local cache)' in line:
nsd_name = line.split()[2]
fsname = 'lroc'
servers = (line.split()[3]).split(',')
nsd_name = line.split()[2]
fsname = 'free'
servers = (line.split()[3]).split(',')
else:
nsd_name = line.split()[1]
fsname = line.split()[0]
servers = (line.split()[2]).split(',')
fsdevs[fsname] = 1
self.nsds[nsd_name] = {}
self.nsds[nsd_name]['usage'] = fsname
self.nsds[nsd_name]['servers'] = servers
try:
del fsname
del servers
except NameError:
pass
class Cluster:
"""
This class will collect the information about the cluster.
"""
def __init__( self ):
self.debug = 0
self.get_cluster_info()
self.nsds = Nsds()
self.gpfsdevs = self.nsds.return_gpfs_devices()
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def get_cluster_info( self ):
"""
This routine parses the mmlscluster command.
"""
self.cluster_info = {}
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmlscluster")
found_nodes = 0
for line in cmd_out.splitlines():
line.rstrip()
# Ignore blank lines
if not line:
continue
# Ignore dashed lines
if '----------' in line:
continue
if '==========' in line:
continue
if found_nodes >= 1:
nodeid = line.split()[0]
daemonname = line.split()[1]
ipaddr = line.split()[2]
adminname = line.split()[3]
self.cluster_info['nodes'][nodeid] = {}
self.cluster_info['nodes'][nodeid]['daemon_name'] = daemonname
self.cluster_info['nodes'][nodeid]['ip'] = ipaddr
self.cluster_info['nodes'][nodeid]['admin_name'] = adminname
if 'Node Daemon' in line:
found_nodes = found_nodes + 1
self.cluster_info['nodes'] = {}
if 'GPFS cluster name' in line:
self.cluster_info['name'] = line.split()[3]
if 'GPFS cluster id' in line:
self.cluster_info['id'] = line.split()[3]
if 'GPFS UID domain' in line:
self.cluster_info['uid'] = line.split()[3]
if 'Remote shell command' in line:
self.cluster_info['rsh'] = line.split()[3]
if 'Remote file copy command' in line:
self.cluster_info['rcp'] = line.split()[4]
if 'Primary server' in line:
self.cluster_info['primary'] = line.split()[2]
if 'Secondary server' in line:
self.cluster_info['secondary'] = line.split()[2]
def dump( self ):
print("Cluster Information")
for key in self.cluster_info.keys():
print("{0} -> {1}".format(key, self.cluster_info[key]))
print("\nNSD Information")
for key in self.nsd_info.keys():
print("{0} -> FS: {1} Servers: {2}".format(key, self.nsd_info[key]['usage'], self.nsd_info[key]['servers']))
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
class StoragePool:
def __init__( self, gpfsdev ):
self.gpfsdev = gpfsdev
self.pools = {}
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmlspool {}".format( self.gpfsdev))
for line in cmd_out.splitlines()[2:]:
line.rstrip()
# Ignore blank lines
if not line:
continue
newline = remove_special_characters( line )
vals = newline.split()
poolname = vals[0]
self.pools[poolname] = {}
self.pools[poolname]['id'] = vals[1]
self.pools[poolname]['blksize'] = vals[2]
self.pools[poolname]['blkmod'] = vals[3]
self.pools[poolname]['data'] = vals[4]
self.pools[poolname]['metadata'] = vals[5]
self.pools[poolname]['datasize'] = vals[6]
self.pools[poolname]['datafree'] = vals[7]
self.pools[poolname]['datapctfree'] = vals[8]
self.pools[poolname]['metasize'] = vals[9]
self.pools[poolname]['metafree'] = vals[10]
self.pools[poolname]['metapctfree'] = vals[11]
self.pool_list = self.pools.keys()
def dump( self ):
print("{}".format(self.pools))
def __getitem__( self, key ):
return self.pools[key]
class Snapshots:
def __init__( self, gpfsdev, fileset='' ):
self.gpfsdev = gpfsdev
self.fileset = fileset
self.snap_name_separator = '_'
self.snapshots = {}
if self.fileset == '':
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmlssnapshot {0} -Y".format( self.gpfsdev ))
else:
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmlssnapshot {0} -j {1} -Y".format( self.gpfsdev, self.fileset ))
# Process the HEADER line
if 'No snapshots in file system' in cmd_out.splitlines()[0]:
self.snap_count = 0
return
keys = cmd_out.splitlines()[0].split(':')[6:]
for line in cmd_out.splitlines()[1:]:
line.rstrip()
# Ignore blank lines
if not line:
continue
vals = line.split(':')[6:]
sname = vals[1]
self.snapshots[sname] = {}
for idx in range(len(vals)-1):
self.snapshots[sname][keys[idx]] = replace_encoded_strings( vals[idx] )
snaplist = self.snapshots.keys()
self.snaplist = sorted( snaplist )
self.snap_count = len( snaplist )
"""
Given an integer of how many snapshots you want to keep, this will return a list of snapshot
names that are to be purged. It does not purge them, only a list of what needs to be purged
based on how many you want to keep.
"""
if self.snap_count > max_to_keep:
self.dellist = list(self.snaplist)[ : -( max_to_keep ) ]
return self.dellist
def delsnap( self, snap_name ):
"""
Given a specific snapshot name, this routine will execute mmdelsnapshot and return you the output
from the command. The object already knows if it is a filesystem or a fileset snapshot, so you just
need to specify the snapshot name.
"""
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmdelsnapshot {} {}".format(self.gpfsdev, snap_name))
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmdelsnapshot {} {} -j {}".format(self.gpfsdev, snap_name, self.fileset))
return cmd_out
def snap( self ):
"""
This code will create a snapshot of the specified filesystem or fileset.
NOTE: You can NOT mix filesystem and fileset snapshots on the same GPFS device.
Filesystem snapshots are named: CCYYMMDD==HHMM for easy sorting / processing.
Filesystem snapshots are named: <Fileset>==CCYYMMDD==HHMM for easy processing again.
"""
if self.fileset == '':
snapname = time.strftime("%Y%m%d") + self.snap_name_separator + time.strftime("%H%M")
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmcrsnapshot {0} {1}".format( self.gpfsdev, snapname ))
else:
snapname = self.fileset + self.snap_name_separator + time.strftime("%Y%m%d") + self.snap_name_separator + time.strftime("%H%M")
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmcrsnapshot {0} {1} -j {2}".format( self.gpfsdev, snapname, self.fileset ))
class Filesystem:
"""
This class will collect the information about the specified GPFS device.
"""
filesystem_defaults = { 'automaticMountOption': 'yes',
'defaultMetadataReplicas': '1',
'maxMetadataReplicas': '2',
'defaultDataReplicas': '1',
'maxDataReplicas': '2',
'blockAllocationType': '',
'fileLockingSemantics': '',
}
def __init__( self, gpfsdev ):
if not gpfsdev:
raise ValueError('NoDevice')
else:
self.gpfsdev = gpfsdev
self.get_filesystem_information()
self.get_fileset_information()
def print_keys( self ):
keys = self.filesys.keys()
return keys
def get_pool_information( self ):
self.pools = StoragePool( self.gpfsdev )
def get_filesystem_information( self ):
self.filesys = {}
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmlsfs {0} -Y".format(self.gpfsdev))
for line in cmd_out.splitlines():
line.rstrip()
# Ignore blank lines
if not line:
continue
# Ignore HEADER line
if 'HEADER' in line:
continue
key = line.split(':')[7]
value = line.split(':')[8]
self.filesys[key] = replace_encoded_strings( value )
def fileset_list( self ):
"""
Return all of the fileset names in the file system.
"""
return self.filesets.keys()
def independent_inode_fileset( self, fname ):
if self.filesets[fname]['fstype'] == 'Independent':
return True
else:
return False
def get_fileset_information( self ):
self.filesets = {}
cmd_out = run_cmd("/usr/lpp/mmfs/bin/mmlsfileset {0} -Y".format(self.gpfsdev))
# Process the HEADER line
keys = cmd_out.splitlines()[0].split(':')[7:]
for line in cmd_out.splitlines()[1:]:
line.rstrip()
# Ignore blank lines
if not line:
continue
vals = line.split(':')[7:]
fname = vals[0]
self.filesets[fname] = {}
for idx in range(len(vals)-1):
self.filesets[fname][keys[idx]] = replace_encoded_strings( vals[idx] )
# Set the fileset type. independent inode or dependent inode
if self.filesets[fname]['filesetName'] == 'root' and self.filesets[fname]['inodeSpace'] == '0':
self.filesets[fname]['fstype'] = 'Independent'
elif self.filesets[fname]['inodeSpace'] >= '1':
self.filesets[fname]['fstype'] = 'Independent'
elif self.filesets[fname]['inodeSpace'] == '0':
self.filesets[fname]['fstype'] = 'Dependent'
else:
self.filesets[fname]['fstype'] = 'Unknown'
@classmethod
def Create( self, gpfsdev, fsname ):
"""
This function will create a new filesystem.
Input 1: A dictionary containing the nsd's and their parameters.
mydisks['nsd1']['usage']='dataAndMetadata'
mydisks['nsd1']['failuregroup']='-1'
mydisks['nsd1']['pool']='system'
Input 2: A dictionary containing parameters for the file system.
"""
print("Creating {0} on {1}".format(fsname, gpfsdev))
self = Filesystem( gpfsdev )
return self
def __getitem__( self, key ):
return self.filesys[key]
if __name__ == '__main__':
#
# This is just where I do my testing of stuff.
#
sys.exit(0)

Chad Kerner
committed
snap = Snapshots( 'condo', 'root' )
#myFS = Filesystem( 'condo' )
#fslist = myFS.fileset_list()
#print("{}".format(myFS.filesets))

Chad Kerner
committed
#newfs = Filesystem.Create( 'fs0', 'chad' )

Chad Kerner
committed
#Clstr = Cluster()

Chad Kerner
committed
#print(Clstr.gpfsdevs)
#myFs = Filesystem( 'wvu' )
#FSa = Filesystem( 'des003' )

Chad Kerner
committed
#print(myFs['disks'])
#print(FSa['disks'])

Chad Kerner
committed
#try:
# F = Filesystem('')
#except:
# print("No Filesystem device specified.")
#else:
# print(F[disks])