pymtt
 All Classes Namespaces Files Functions Variables Groups
IPMITool.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2016-2018 Intel, Inc. All rights reserved.
4 # $COPYRIGHT$
5 #
6 # Additional copyrights may follow
7 #
8 # $HEADER$
9 #
10 
11 import os
12 import sys
13 try:
14  from Queue import *
15 except:
16  from queue import *
17 import threading
18 from CNCMTTTool import *
19 
20 class workerThread(threading.Thread):
21  def __init__(self, threadID, queue, status, lock, testDef):
22  threading.Thread.__init__(self)
23  self.threadID = threadID
24  self.queue = queue
25  self.lock = lock
26  self.status = status
27  self.testDef = testDef
28  return
29 
30  def run(self):
31  self.testDef.logger.verbose_print("IPMITool: Thread " + str(self.threadID) + " is active")
32  while True:
33  self.lock.acquire()
34  if not self.queue.empty():
35  task = self.queue.get()
36  self.lock.release()
37  self.testDef.logger.verbose_print("IPMITool: Thread " + str(self.threadID) + " received task " + ' '.join(task['cmd']))
38  # we should have received a dictionary - check for dryrun
39  dryrun = False
40  try:
41  if task['dryrun']:
42  dryrun = True
43  except:
44  pass
45  # check the cmd
46  try:
47  if task['reset']:
48  if dryrun:
49  # just record a result
50  self.testDef.logger.verbose_print("IPMITool: Thread " + str(self.threadID) + " dryrun reset " + task['target'])
51  self.lock.acquire()
52  self.status.append((0, ' '.join(task['cmd']), None))
53  self.lock.release()
54  continue
55  # ping until we get a response
56  ntries = 0
57  while True:
58  ++ntries
59  status,stdout,stderr,_ = self.testDef.execmd.execute(None, task['cmd'], self.testDef)
60  if 0 == status or ntries == task['maxtries']:
61  self.testDef.logger.verbose_print("IPMITool: node " + task['target'] + " is back")
62  break
63  # record the result
64  self.lock.acquire()
65  if 0 != status and ntries >= task['maxtries']:
66  msg = "Operation timed out on node " + task['target']
67  self.status.append((-1, ' '.join(task['cmd']), msg))
68  else:
69  self.status.append((status, ' '.join(task['cmd']), stderr))
70  self.lock.release()
71  continue
72  except:
73  try:
74  if task['cmd'] is not None:
75  if dryrun:
76  # just record a result
77  self.testDef.logger.verbose_print("IPMITool: Thread " + str(self.threadID) + " dryrun " + ' '.join(task['cmd']))
78  self.lock.acquire()
79  self.status.append((0, ' '.join(task['cmd']), None))
80  # add reset command if required
81  try:
82  if task['target'] is not None:
83  # add the reset command to the queue
84  ckcmd = {}
85  ckcmd['reset'] = True
86  ckcmd['cmd'] = ["ping", "-c", "1", task['target']]
87  ckcmd['maxtries'] = task['maxtries']
88  ckcmd['target'] = task['target'] # just for debug purposes
89  ckcmd['dryrun'] = dryrun
90  # add it to the queue
91  self.queue.put(ckcmd)
92  except:
93  pass
94  self.lock.release()
95  continue
96  # send it off to ipmitool to execute
97  self.testDef.logger.verbose_print("IPMITool: " + ' '.join(task['cmd']))
98  st,stdout,stderr,_ = self.testDef.execmd.execute(None, task['cmd'], self.testDef)
99  self.lock.acquire()
100  self.status.append((st, ' '.join(task['cmd']), stderr))
101  try:
102  if task['target'] is not None:
103  # add the reset command to the queue
104  ckcmd['reset'] = True
105  ckcmd['cmd'] = ["ping", "-c", "1", task['target']]
106  ckcmd['maxtries'] = task['maxtries']
107  ckcmd['target'] = task['target'] # just for debug purposes
108  ckcmd['dryrun'] = dryrun
109  # add it to the queue
110  self.queue.put(ckcmd)
111  except:
112  pass
113  self.lock.release()
114  continue
115  else:
116  # mark as a bad command
117  self.lock.acquire()
118  self.status.append((2, "NULL", "Missing command"))
119  self.lock.release()
120  continue
121  except:
122  # bad input
123  self.lock.acquire()
124  self.status.append((2, "NULL", "Missing command"))
125  self.lock.release()
126  continue
127  else:
128  # if the queue is empty, then we are done
129  self.lock.release()
130  return
131 
132 ## @addtogroup Tools
133 # @{
134 # @addtogroup CNC
135 # @section IPMITool
136 # Interface to the ipmitool cmd line
137 # @param target List of remote host names or LAN interfaces to monitor during reset operations
138 # @param controller List of IP addresses of remote node controllers/BMCs
139 # @param username Remote session username
140 # @param password Remote session password
141 # @param pwfile File containing remote session password
142 # @param command Command to be sent
143 # @param maxtries Max number of times to ping each host before declaring reset to fail
144 # @param numthreads Number of worker threads to use
145 # @param dryrun Dryrun - print out commands but do not execute
146 # @param sudo Use sudo to exeute privilaged comands
147 # @}
149  def __init__(self):
150  CNCMTTTool.__init__(self)
151  self.options = {}
152  self.options['target'] = (None, "List of remote host names or LAN interfaces to monitor during reset operations")
153  self.options['controller'] = (None, "List of IP addresses of remote node controllers/BMCs")
154  self.options['username'] = (None, "Remote controller username")
155  self.options['password'] = (None, "Remote controller password")
156  self.options['pwfile'] = (None, "File containing remote controller password")
157  self.options['command'] = (None, "Command to be sent")
158  self.options['maxtries'] = (100, "Max number of times to ping each host before declaring reset to fail")
159  self.options['numthreads'] = (30, "Number of worker threads to use")
160  self.options['dryrun'] = (False, "Dryrun - print out commands but do not execute")
161  self.options['sudo'] = (False, "Use sudo to execute privileged commands")
162  self.lock = threading.Lock()
163  self.threads = []
164  self.threadID = 0
165  self.status = []
166  return
167 
168  def activate(self):
169  # use the automatic procedure from IPlugin
170  IPlugin.activate(self)
171  return
172 
173  def deactivate(self):
174  IPlugin.deactivate(self)
175 
176  def print_name(self):
177  return "IPMITool"
178 
179  def print_options(self, testDef, prefix):
180  lines = testDef.printOptions(self.options)
181  for line in lines:
182  print(prefix + line)
183  return
184 
185  def execute(self, log, keyvals, testDef):
186  testDef.logger.verbose_print("IPMITool execute")
187  # check for a modules directive
188  mods = None
189  try:
190  if keyvals['modules'] is not None:
191  if testDef.modcmd is None:
192  # cannot execute this request
193  log['stderr'] = "No module support available"
194  log['status'] = 1
195  return
196  # create a list of the requested modules
197  mods = keyvals['modules'].split(',')
198  # have them loaded
199  status,stdout,stderr = testDef.modcmd.loadModules(mods, testDef)
200  if 0 != status:
201  log['status'] = status
202  log['stdout'] = stdout
203  log['stderr'] = stderr
204  return
205  modloaded = True
206  except KeyError:
207  pass
208 
209  # parse what we were given against our defined options
210  cmds = {}
211  testDef.parseOptions(log, self.options, keyvals, cmds)
212  testDef.logger.verbose_print("IPMITool: " + ' '.join(cmds))
213  # must have given us at least one controller address
214  try:
215  if cmds['controller'] is None:
216  log['status'] = 1
217  log['stderr'] = "No target controllers identified"
218  return
219  else:
220  # convert to a list
221  controllers = cmds['controller'].split(',')
222  except:
223  log['status'] = 1
224  log['stderr'] = "No target controllers identified"
225  return
226  # and a command
227  try:
228  if cmds['command'] is None:
229  log['status'] = 1
230  log['stderr'] = "No IPMI command given"
231  return
232  except:
233  log['status'] = 1
234  log['stderr'] = "No IPMI command given"
235  return
236  # if this is a reset command, then we must have targets
237  # for each controller
238  try:
239  if cmds['target'] is None:
240  log['status'] = 1
241  log['stderr'] = "No target nodes identified"
242  return
243  else:
244  # convert it to a list
245  targets = cmds['target'].split(",")
246  # must match number of controllers
247  if len(targets) != len(controllers):
248  log['status'] = 1
249  log['stderr'] = "Number of targets doesn't equal number of controllers"
250  return
251  reset = True
252  except:
253  reset = False
254  # Setup the queue - we need a spot for the command to be sent to each
255  # identified target. If it is a command that will result in cycling
256  # the node, then we need to double it so we can execute the loop of
257  # "ping" commands to detect node restart
258  if reset:
259  ipmiQueue = Queue(2 * len(controllers))
260  else:
261  ipmiQueue = Queue(len(controllers))
262 
263  # Fill the queue
264  self.lock.acquire()
265  for n in range(0, len(controllers)):
266  # construct the cmd
267  cmd = {}
268  ipmicmd = cmds['command']
269  ipmicmd.insert(0, "chassis")
270  ipmicmd.insert(0, "ipmitool")
271  if cmds['sudo']:
272  ipmicmd.insert(0, "sudo")
273  ipmicmd.append("-H")
274  ipmicmd.append(controllers[n])
275  if cmds['username'] is not None:
276  ipmicmd.append("-U")
277  ipmicmd.append(cmds['username'])
278  if cmds['password'] is not None:
279  ipmicmd.append("-P")
280  ipmicmd.append(cmds['password'])
281  try:
282  if cmds['pwfile'] is not None:
283  if os.path.exists(cmds['pwfile']):
284  f = open(cmds['pwfile'], 'r')
285  password = f.readline().strip()
286  ipmicmd.append("-P")
287  ipmicmd.append(password)
288  f.close()
289  else:
290  log['stdout'] = None
291  log['status'] = 1;
292  log['stderr'] = "Password file " + cmds['pwfile'] + " does not exist"
293  return
294  except KeyError:
295  pass
296  cmd['cmd'] = ipmicmd
297  if reset:
298  cmd['target'] = targets[n]
299  cmd['maxtries'] = cmds['maxtries']
300  cmd['dryrun'] = cmds['dryrun']
301  # add it to the queue
302  ipmiQueue.put(cmd)
303  # setup the response
304  self.status = []
305  # spin up the threads
306  self.threads = []
307  # release the queue
308  self.lock.release()
309  if len(targets) < self.options['numthreads']:
310  rng = len(targets)
311  else:
312  rng = self.options['numthreads']
313  for n in range(0, rng):
314  thread = workerThread(self.threadID, ipmiQueue, self.status, self.lock, testDef)
315  thread.start()
316  self.threads.append(thread)
317  self.threadID += 1
318  # wait for completion
319  while not ipmiQueue.empty():
320  pass
321  # wait for all threads to complete/terminate
322  for t in self.threads:
323  t.join()
324  # set our default log
325  log['status'] = 0
326  log['stdout'] = None
327  log['stderr'] = None
328  # determine our final status - if any of the steps failed, then
329  # set the status to the first one that did
330  for st in self.status:
331  if 0 != st[0]:
332  log['status'] = st[0]
333  log['stdout'] = st[1]
334  log['stderr'] = st[2]
335  # reset modules if necessary
336  if mods is not None:
337  testDef.modcmd.unloadModules(mods, testDef)
338  return