pymtt
 All Classes Namespaces Files Functions Variables Groups
Hostfile.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2015-2018 Intel, Inc. All rights reserved.
4 # $COPYRIGHT$
5 #
6 # Additional copyrights may follow
7 #
8 # $HEADER$
9 #
10 
11 from __future__ import print_function
12 import os
13 import re
14 from BuildMTTTool import *
15 import hostlist # python-hostlist package
16 import shlex
17 import math
18 
19 ## @addtogroup Tools
20 # @{
21 # @addtogroup Build
22 # @section Hostfile
23 # Builds a hostfile based on a nodelist
24 # @param parent Section that precedes this one in the dependency tree
25 # @param nodelist list of nodes to create hostfile from
26 # @param hostfile name of hostfile to generate
27 # @}
29  def __init__(self):
30  BuildMTTTool.__init__(self)
31  self.activated = False
32  self.options = {}
33  self.options['parent'] = (None, "Section that precedes this one in the dependency tree")
34  self.options['nodelist'] = (None, "List of nodes to create hostfile from")
35  self.options['nodeuse_ratio'] = (None, "Fraction of nodes to use")
36  self.options['hostfile'] = (None, "Name of hostfile to generate")
37  self.options['nodestatus_cmd'] = ("sinfo -N -h", "Command to print status of nodes")
38  self.options['nodename_column'] = ("0", "Column index from nodestatus_cmd where nodename resides")
39  self.options['nodestatus_column'] = ("3", "Column index from nodestatus_cmd where status of node resides")
40  self.options['idle_status_string'] = ("idle", "String to check for when checking node status")
41  return
42 
43  def activate(self):
44  if not self.activated:
45  # use the automatic procedure from IPlugin
46  IPlugin.activate(self)
47  self.activated = True
48  return
49 
50  def deactivate(self):
51  if self.activated:
52  IPlugin.deactivate(self)
53  self.activated = False
54  return
55 
56  def print_name(self):
57  return "Hostfile"
58 
59  def print_options(self, testDef, prefix):
60  lines = testDef.printOptions(self.options)
61  for line in lines:
62  print(prefix + line)
63  return
64 
65  def execute(self, log, keyvals, testDef):
66  testDef.logger.verbose_print("Hostfile Execute")
67  # parse any provided options - these will override the defaults
68  cmds = {}
69  testDef.parseOptions(log, self.options, keyvals, cmds)
70 
71  # If they didn't give us a hostfile and nodelist (or nodeuse_ratio), then error out
72 
73  try:
74  wrong_input_stderr = ""
75  if cmds['hostfile'] is None:
76  wrong_input_stderr += "No hostfile specified. "
77  if cmds['nodelist'] is None and cmds['nodeuse_ratio'] is None:
78  wrong_input_stderr += "No nodelist specified, or nodeuse_ratio specified. "
79  if wrong_input_stderr:
80  log['status'] = 1
81  log['stderr'] = wrong_input_stderr
82  return
83  except KeyError:
84  log['status'] = 1
85  log['stderr'] = "Required input not in cmds"
86  return
87 
88  # get the location of the software we are to build
89  parentlog = None
90  try:
91  if cmds['parent'] is not None:
92  # we have to retrieve the log entry from
93  # the parent section so we can get the
94  # location of the package. The logger
95  # can provide it for us
96  parentlog = testDef.logger.getLog(cmds['parent'])
97  if parentlog is None:
98  log['status'] = 1
99  log['stderr'] = "Parent",cmds['parent'],"log not found"
100  return
101  except KeyError:
102  log['status'] = 1
103  log['stderr'] = "Parent not specified"
104  return
105  try:
106  parentloc = os.path.join(os.getcwd(),log['options']['scratch'])
107  location = parentloc
108  except KeyError:
109  log['status'] = 1
110  log['stderr'] = "No scratch directory in log"
111  return
112  if parentlog is not None:
113  try:
114  parentloc = parentlog['location']
115  location = parentloc
116  except KeyError:
117  log['status'] = 1
118  log['stderr'] = "Location of package to build was not specified in parent stage"
119  return
120  else:
121  try:
122  if log['section'].startswith("TestGet:") or log['section'].startswith("MiddlewareGet:"):
123  location = os.path.join(parentloc,log['section'].replace(":","_"))
124  except KeyError:
125  log['status'] = 1
126  log['stderr'] = "No section in log"
127  return
128 
129  # check to see if this is a dryrun
130  if testDef.options['dryrun']:
131  # just log success and return
132  log['status'] = 0
133  return
134 
135  # Check to see if this needs to be ran if ASIS is specified
136  try:
137  if cmds['asis']:
138  if os.path.exists(os.path.join(location,cmds['hostfile'])):
139  testDef.logger.verbose_print("hostfile " + os.path.join(location,cmds['hostfile']) + " exists. Skipping...")
140  log['location'] = location
141  log['status'] = 0
142  return
143  else:
144  testDef.logger.verbose_print("hostfile " + os.path.join(location,cmds['hostfile']) + " does not exist. Continuing...")
145  except KeyError:
146  pass
147 
148  # save the current directory so we can return to it
149  cwd = os.getcwd()
150  # now move to the package location
151  if not os.path.exists(location):
152  os.makedirs(location)
153  os.chdir(location)
154 
155  ################################
156  # Execute Plugin
157  ################################
158  status, stdout, stderr, time = testDef.execmd.execute(cmds, shlex.split(cmds['nodestatus_cmd']), testDef)
159  if status != 0:
160  log['status'] = 1
161  log['stderr'] = "Command %s failed: %s" % (cmds['nodestatus_cmd'], " ".join(stderr))
162  os.chdir(cwd)
163  return
164  stdout_split = [l.split() for l in stdout]
165  try:
166  nodename_column = int(cmds['nodename_column'])
167  except ValueError:
168  log['status'] = 1
169  log['stderr'] = "Invalid nodename_column %s -- must be an integer" % cmds['nodename_column']
170  os.chdir(cwd)
171  return
172  try:
173  nodestatus_column = int(cmds['nodestatus_column'])
174  except ValueError:
175  log['status'] = 1
176  log['stderr'] = "Invalid nodestatus_column %s -- must be an integer" % cmds['nodestatus_column']
177  try:
178  node_status = {l[nodename_column]: l[nodestatus_column] for l in stdout_split}
179  except IndexError:
180  log['status'] = 1
181  log['stderr'] = ""
182  if nodename_column < 0 or nodename_column >= len(stdout_split[0]):
183  log['stderr'] += "nodename_column is out of bounds: %s " % str(nodename_column)
184  if nodestatus_column < 0 or nodestatus_column >= len(stdout_split[0]):
185  log['stderr'] += "nodestatus_column is out of bounds: %s " % str(nodestatus_column)
186  os.chdir(cwd)
187  return
188 
189  if cmds['nodelist'] is not None:
190  # Create hostlist from nodelist
191  try:
192  hosts = hostlist.expand_hostlist(cmds['nodelist'])
193  except hostlist.BadHostlist:
194  log['status'] = 1
195  log['stderr'] = "Bad nodelist format: %s" % cmds['nodelist']
196  os.chdir(cwd)
197  return
198  else:
199  # use all hosts
200  hosts = [l[nodename_column] for l in stdout_split]
201 
202  # Use a ratio of the nodes
203  if cmds['nodeuse_ratio'] is not None:
204  try:
205  ratio = float(cmds['nodeuse_ratio'])
206  except:
207  try:
208  ratio = float(cmds['nodeuse_ratio'].split("/")[0])/float(cmds['nodeuse_ratio'].split("/")[1])
209  except:
210  log['status'] = 1
211  log['stderr'] = "Bad nodeuse_ratio format: %s" % cmds['nodeuse_ratio']
212  os.chdir(cwd)
213  return
214  idle_hosts = [h for h in hosts if node_status[h] == cmds['idle_status_string']]
215  nonidle_hosts = [h for h in hosts if node_status[h] != cmds['idle_status_string']]
216  num_nodes = int(math.ceil(float(len(hosts))*ratio))
217  if num_nodes <= len(idle_hosts):
218  hosts = idle_hosts[:num_nodes]
219  else:
220  hosts = idle_hosts + nonidle_hosts[:num_nodes - len(idle_hosts)]
221 
222  # Create hostfile from hostlist
223  try:
224  with open(cmds['hostfile'], "w") as f:
225  f.write("\n".join(hosts))
226  f.close()
227  except IOError:
228  log['status'] = 1
229  log['stderr'] = "File exception when writing hostfile %s" % cmds['hostfile']
230  os.chdir(cwd)
231  return
232 
233  log['status'] = 0
234  log['stdout'] = "Creation of hostfile %s from nodelist %s success" % (cmds['hostfile'],cmds['nodelist'])
235  log['location'] = location
236 
237  # return to original location
238  os.chdir(cwd)
239  return