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.
# 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
class Cluster:
"""
This class will collect the information about the cluster.
"""
def __init__( self ):
self.get_cluster_info()
self.get_nsd_info()
def get_nsd_info( self ):
"""
This routing parses the mmlsnsd command.
"""
self.nsd_info = {}
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 'free disk' in line:
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.nsd_info[nsd_name] = {}
self.nsd_info[nsd_name]['usage'] = fsname
self.nsd_info[nsd_name]['servers'] = servers
self.gpfsdevs = fsdevs.keys()
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 ):
if debug == 1:
print("Cluster Information")
for key in self.cluster_info.keys():
print("{0} -> {1}".format(key, self.cluster_info[key]))
if debug == 2:
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']))
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
211
212
213
214
215
216
217
218
219
220
def remove_special_characters( mystring ):
tempstring = mystring.replace('%', '')
mystring = tempstring
tempstring = mystring.replace('(', '')
mystring = tempstring
tempstring = mystring.replace(')', '')
return tempstring
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])