Changeset 3545
- Timestamp:
- 10/18/2009 09:09:13 PM (1 month ago)
- Files:
-
- gip/trunk/gip/lib/python/condor_common.py (modified) (2 diffs)
- gip/trunk/gip/lib/python/gip/batch_systems/condor.py (added)
- gip/trunk/gip/lib/python/gip/batch_systems/condor_handlers.py (added)
- gip/trunk/gip/lib/python/gip/batch_systems/sge.py (added)
- gip/trunk/gip/lib/python/gip/batch_systems/sge_sax_handler.py (moved) (moved from gip/trunk/gip/lib/python/sge_sax_handler.py) (2 diffs)
- gip/trunk/gip/lib/python/gip/providers/generic_batch_system.py (modified) (9 diffs)
- gip/trunk/gip/lib/python/gip_testing.py (modified) (2 diffs)
- gip/trunk/gip/lib/python/sge_common.py (modified) (2 diffs)
- gip/trunk/gip/providers/batch_system.py (modified) (2 diffs)
- gip/trunk/test/command_output/commands (modified) (1 diff)
- gip/trunk/test/command_output/condor_status_submitter_xml_pf2 (copied) (copied from gip/trunk/test/command_output/condor_status_submitter_xml) (1 diff)
- gip/trunk/test/condor_test.py (modified) (2 diffs)
- gip/trunk/test/lsf_test.py (modified) (2 diffs)
- gip/trunk/test/sge_test.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
gip/trunk/gip/lib/python/condor_common.py
r3200 r3545 16 16 import types 17 17 18 import xml19 from xml.sax import make_parser, SAXParseException20 from xml.sax.handler import ContentHandler, feature_external_ges21 22 18 from gip_common import voList, cp_getBoolean, getLogger, cp_get, voList, \ 23 19 VoMapper, cp_getInt 24 from gip _testing import runCommand20 from gip.batch_systems.condor_handlers import * 25 21 26 22 condor_version = "condor_version" 27 condor_group = "condor_config_val GROUP_NAMES"28 condor_quota = "condor_config_val GROUP_QUOTA_%(group)s"29 condor_prio = "condor_config_val GROUP_PRIO_FACTOR_%(group)s"23 condor_group = "condor_config_val -negotiator GROUP_NAMES" 24 condor_quota = "condor_config_val -negotiator GROUP_QUOTA_%(group)s" 25 condor_prio = "condor_config_val -negotiator GROUP_PRIO_FACTOR_%(group)s" 30 26 condor_status = "condor_status -xml -constraint '%(constraint)s'" 31 27 condor_status_submitter = "condor_status -submitter -xml" … … 33 29 34 30 log = getLogger("GIP.Condor") 35 36 class ClassAdParser(ContentHandler):37 """38 Streaming SAX handler for the output of condor_* -xml calls; it's around39 60 times faster and has a similar reduction in required memory.40 41 Use this as a ContentHandler for a SAX parser; call getJobInfo afterward42 to get the information about each job.43 44 getJobInfo returns a dictionary of jobs; the key for the dictionary is the45 Condor attribute passed in as 'idx' to the constructor; the value is another46 dictionary of key-value pairs from the condor JDL, where the keys is in47 the attribute list passed to the constructor.48 """49 50 def __init__(self, idx, attrlist=None): #pylint: disable-msg=W023151 """52 @param idx: The attribute name used to index the classads with.53 @keyword attrlist: A list of attributes to record; if it is empty, then54 parse all attributes.55 """56 if not attrlist:57 self.attrlist = []58 else:59 self.attrlist = list(attrlist)60 if isinstance(idx, types.TupleType) and self.attrlist:61 for name in idx:62 if name not in self.attrlist:63 self.attrlist.append(name)64 if self.attrlist and idx not in self.attrlist:65 self.attrlist.append(idx)66 self.idxAttr = idx67 self.caInfo = {}68 self.attrInfo = ''69 # Initialize some used class variables.70 self._starttime = time.time()71 self._endtime = time.time()72 self._elapsed = 073 self.curCaInfo = {}74 self.attrName = ''75 76 def startDocument(self):77 """78 Start up a parsing sequence; initialize myself.79 """80 self.attrInfo = ''81 self.caInfo = {}82 self._starttime = time.time()83 84 def endDocument(self):85 """86 Print out debugging information from this document parsing.87 """88 self._endtime = time.time()89 self._elapsed = self._endtime - self._starttime90 myLen = len(self.caInfo)91 log.info("Processed %i classads in %.2f seconds; %.2f classads/" \92 "second" % (myLen,93 self._elapsed, myLen/(self._elapsed+1e-10)))94 95 def startElement(self, name, attrs):96 """97 Open an XML element - take note if its a 'c', for the start of a new98 classad, or an 'a', the start of a new attribute.99 """100 if name == 'c':101 self.curCaInfo = {}102 elif name == 'a':103 self.attrName = str(attrs.get('n', 'Unknown'))104 self.attrInfo = ''105 else:106 pass107 108 def endElement(self, name):109 """110 End of an XML element - save everything we learned111 """112 if name == 'c':113 if isinstance(self.idxAttr, types.TupleType):114 full_idx = ()115 for idx in self.idxAttr:116 idx = self.curCaInfo.get(idx, None)117 if idx:118 full_idx += (idx,)119 if len(full_idx) == len(self.idxAttr):120 self.caInfo[full_idx] = self.curCaInfo121 else:122 idx = self.curCaInfo.get(self.idxAttr, None)123 if idx:124 self.caInfo[idx] = self.curCaInfo125 elif name == 'a':126 if self.attrName in self.attrlist or len(self.attrlist) == 0:127 self.curCaInfo[self.attrName] = self.attrInfo128 else:129 pass130 131 def characters(self, ch):132 """133 Save up the XML characters found in the attribute.134 """135 self.attrInfo += str(ch)136 137 def getClassAds(self):138 """139 Returns a dictionary of dictionaries consisting of all the classAds140 and their attributes.141 """142 return self.caInfo143 144 def parseCondorXml(fp, handler): #pylint: disable-msg=C0103145 """146 Parse XML from Condor.147 148 Create a SAX parser with the content handler B{handler}, then parse the149 contents of B{fp} with it.150 151 @param fp: A file-like object of the Condor XML data152 @param handler: An object which will be our content handler.153 @type handler: xml.sax.handler.ContentHandler154 @returns: None155 """156 parser = make_parser()157 parser.setContentHandler(handler)158 try:159 parser.setFeature(feature_external_ges, False)160 except xml.sax._exceptions.SAXNotRecognizedException:161 pass162 163 try:164 parser.parse(fp)165 except SAXParseException, e:166 if e.getMessage() == 'no element found':167 pass168 else:169 raise170 171 def condorCommand(command, cp, info=None): #pylint: disable-msg=W0613172 """173 Execute a command in the shell. Returns a file-like object174 containing the stdout of the command175 176 Use this function instead of executing directly (os.popen); this will177 allow you to hook your providers into the testing framework.178 179 @param command: The command to execute180 @param cp: The GIP configuration object181 @keyword info: A dictionary-like object for Python string substitution182 @returns: a file-like object.183 """184 185 # must test for empty dict for special cases like the condor_status186 # command which has -format '%s' arguments. Python will try to do187 # the string substitutions regardless of single quotes188 if info:189 cmd = command % info190 else:191 cmd = command192 log.debug("Running command %s." % cmd)193 194 return runCommand(cmd)195 31 196 32 def getLrmsInfo(cp): #pylint: disable-msg=C0103 gip/trunk/gip/lib/python/gip/batch_systems/sge_sax_handler.py
r3344 r3545 1 2 from gip_testing import runCommand 1 3 from xml.sax.handler import ContentHandler 2 4 … … 99 101 return self.JobList 100 102 103 def sgeOutputFilter(fp): 104 """ 105 SGE's 'qconf' command has a line-continuation format which we will want to 106 parse. To accomplish this, we use this filter on the output file stream. 107 108 You should "scrub" SGE output like this:: 109 110 fp = runCommand(<pbs command>) 111 for line in pbsOutputFilter(fp): 112 ... parse line ... 113 114 Or simply, 115 116 for line in sgeCommand(<pbs command>): 117 ... parse line ... 118 """ 119 class SGEIter: 120 """ 121 An iterator for the SGE output. 122 """ 123 124 def __init__(self, fp): 125 self.fp = fp 126 self.fp_iter = fp.__iter__() 127 self.prevline = '' 128 self.done = False 129 130 def next(self): 131 """ 132 Return the next full line of output for the iterator. 133 """ 134 try: 135 line = self.fp_iter.next() 136 if not line.endswith('\\'): 137 result = self.prevline + line 138 self.prevline = '' 139 return result 140 line = line.strip()[:-1] 141 self.prevline = self.prevline + line 142 return self.next() 143 except StopIteration: 144 if self.prevline: 145 results = self.prevline 146 self.prevline = '' 147 return results 148 raise 149 150 class SGEFilter: 151 """ 152 An iterable object based upon the SGEIter iterator. 153 """ 154 155 def __init__(self, myiter): 156 self.iter = myiter 157 158 def __iter__(self): 159 return self.iter 160 161 return SGEFilter(SGEIter(fp)) 162 163 def sgeCommand(command, cp): 164 """ 165 Run a command against the SGE batch system. 166 167 Use this when talking to SGE; not only does it allow for integration into 168 the GIP test framework, but it also filters and expands SGE-style line 169 continuations. 170 """ 171 fp = runCommand(command) 172 return sgeOutputFilter(fp) 173 174 def convert_time_to_secs(entry, infinity=9999999, error=None): 175 """ 176 Convert the output of a time-related field in SGE to seconds. 177 178 This handles the HH:MM:SS format plus the text "infinity" 179 """ 180 if error == None: 181 error = infinity 182 entry = entry.split(':') 183 if len(entry) == 3: 184 try: 185 hours, mins, secs = int(entry[0]), int(entry[1]), int(entry[2]) 186 except: 187 log.warning("Invalid time entry: %s" % entry) 188 return error 189 return hours*3600 + mins*60 + secs 190 elif len(entry) == 1: 191 entry = entry[0] 192 if entry.lower().find('inf') >= 0: 193 return infinity 194 else: 195 try: 196 return int(entry) 197 except: 198 return infinity 199 else: 200 return error 201 gip/trunk/gip/lib/python/gip/providers/generic_batch_system.py
r3543 r3545 14 14 15 15 from gip.batch_systems.pbs import PbsBatchSystem 16 from gip.batch_systems.condor import CondorBatchSystem 16 17 from gip.batch_systems.forwarding import Forwarding 17 18 … … 31 32 excludeQueues = [] 32 33 vo_queues = batch.getVoQueues() 34 log.info("The following queues were returned from the batch system: %s" % \ 35 ", ".join(queueInfo)) 33 36 for queue, info in queueInfo.items(): 34 37 if queue in excludeQueues: … … 48 51 info['ceUniqueID'] = unique_id 49 52 if "job_slots" not in info: 50 if queue in queueCpus: 51 info['job_slots'] = queueCpus[queue][0] 52 else: 53 info["job_slots"] = totalCpu 53 info['job_slots'] = queueCpus.get(queue, [totalCpu])[0] 54 # INVARIANT: job_slots <= totalCpu 55 info['job_slots'] = min(info['job_slots'], totalCpu) 54 56 if "priority" not in info: 55 57 info["priority"] = 0 56 if "max_running" not in info: 58 # note: if max_running == 0, then it was undefined. 59 if "max_running" not in info or info['max_running'] == 0: 57 60 info["max_running"] = info["job_slots"] 58 61 if "max_wall" not in info: … … 62 65 info.get("wait", 0), max_job_time=info["max_wall"]) 63 66 64 info["job_slots"] = min(totalCpu, info["job_slots"])65 67 info['ert'] = ert 66 68 info['wrt'] = wrt … … 78 80 if 'max_queuable' in info: 79 81 info['max_total'] = info['max_queuable'] 82 # free_slots <= max_queuable 80 83 info['free_slots'] = min(info['free_slots'], info['max_queuable']) 81 84 else: 82 85 info['max_total'] = info['max_waiting'] + info['max_running'] 86 # free_slots <= max_total 83 87 info['free_slots'] = min(info['free_slots'], info['max_total']) 84 88 info['max_slots'] = 1 85 89 86 # Enforce invariants: 87 # max_total <= max_running 90 # INVARIANTS: 91 # assigned <= max_running 92 info['job_slots'] = min(info['job_slots'], info['max_running']) 93 # max_running >= running 94 info['max_running'] = max(info['running'], info['max_running']) 95 # max_total (jobs in system) >= max_running 96 info['max_total'] = max(info['max_total'], info['max_running']) 88 97 # free_slots <= max_running 89 info['max_total'] = min(info['max_total'], info['max_running'])90 98 info['free_slots'] = min(info['free_slots'], info['max_running']) 99 # free_slots <= freeCpu 100 info['free_slots'] = min(info['free_slots'], freeCpu) 101 # free_slots <= job_slots - running, >= 0 102 info['free_slots'] = min(info['free_slots'], info['job_slots'] - \ 103 info['running']) 104 if info['free_slots'] < 0: 105 info['free_slots'] = 0 106 # job_slots >= running 107 info['job_slots'] = max(info['job_slots'], info['running']) 91 108 92 109 info['assigned'] = info['job_slots'] 93 94 # Enforce invariants:95 # assigned <= max_running96 info['assigned'] = min(info['assigned'], info['max_running'])97 110 98 111 info['lrmsType'] = system_name … … 105 118 has_vo = True 106 119 if not has_vo: 120 log.info("No VOs in queue %s; not printing." % queue) 107 121 continue 108 122 info['acbr'] = acbr[:-1] … … 133 147 max_job_time=my_queue_info.get("max_wall", 0)) 134 148 149 max_running = info2.get('max_running', my_queue_info.get('job_slots', 150 0)) 151 max_running = min(max_running, my_queue_info.get('max_running', 99999)) 152 135 153 info = { 136 154 'ceUniqueID' : ce_unique_id, … … 143 161 'job_manager' : system_name, 144 162 'running' : info2.get('running', 0), 145 'max_running' : info2.get('max_running', 0),163 'max_running' : max_running, 146 164 'priority' : queue_info.get(queue, {}).get('priority', 0), 147 165 'waiting' : info2.get('wait', 0), … … 164 182 if impl == 'pbs': 165 183 batch = PbsBatchSystem(cp) 184 elif impl == 'condor': 185 batch = CondorBatchSystem(cp) 166 186 else: 167 187 log.error("Unknown job manager: %s" % impl) gip/trunk/gip/lib/python/gip_testing.py
r2903 r3545 36 36 suffix = m.groups()[0] 37 37 if cmd not in commands: 38 fd = open(os.path.expandvars("$VDT_LOCATION/test/command_output/" \39 "commands"))38 cmd_dir = os.path.expandvars("$VDT_LOCATION/test/command_output/") 39 fd = open(os.path.join(cmd_dir, "commands")) 40 40 for line in fd: 41 41 if line.startswith("#") or len(line.strip()) == 0: … … 45 45 command = command.strip() 46 46 if suffix: 47 command = '%s_%s' % (command, suffix) 47 new_command = '%s_%s' % (command, suffix) 48 if os.path.exists(os.path.join(cmd_dir, new_command)): 49 command = new_command 48 50 commands[val.strip()] = command 49 51 return commands[cmd] gip/trunk/gip/lib/python/sge_common.py
r3344 r3545 21 21 from gip_common import getLogger, VoMapper, voList, parseRvf 22 22 from xml_common import parseXmlSax 23 from sge_sax_handler import QueueInfoParser, JobInfoParser 23 from gip.batch_systems.sge_sax_handler import QueueInfoParser, JobInfoParser, \ 24 sgeCommand 24 25 from gip_testing import runCommand 25 26 from UserDict import UserDict … … 35 36 36 37 # h_rt - hard real time limit (max_walltime) 37 38 39 def sgeOutputFilter(fp):40 """41 SGE's 'qconf' command has a line-continuation format which we will want to42 parse. To accomplish this, we use this filter on the output file stream.43 44 You should "scrub" SGE output like this::45 46 fp = runCommand(<pbs command>)47 for line in pbsOutputFilter(fp):48 ... parse line ...49 50 Or simply,51 52 for line in sgeCommand(<pbs command>):53 ... parse line ...54 """55 class SGEIter:56 """57 An iterator for the SGE output.58 """59 60 def __init__(self, fp):61 self.fp = fp62 self.fp_iter = fp.__iter__()63 self.prevline = ''64 self.done = False65 66 def next(self):67 """68 Return the next full line of output for the iterator.69 """70 try:71 line = self.fp_iter.next()72 if not line.endswith('\\'):73 result = self.prevline + line74 self.prevline = ''75 return result76 line = line.strip()[:-1]77 self.prevline = self.prevline + line78 return self.next()79 except StopIteration:80 if self.prevline:81 results = self.prevline82 self.prevline = ''83 return results84 raise85 86 class SGEFilter:87 """88 An iterable object based upon the SGEIter iterator.89 """90 91 def __init__(self, myiter):92 self.iter = myiter93 94 def __iter__(self):95 return self.iter96 97 return SGEFilter(SGEIter(fp))98 99 def sgeCommand(command, cp):100 """101 Run a command against the SGE batch system.102 103 Use this when talking to SGE; not only does it allow for integration into104 the GIP test framework, but it also filters and expands SGE-style line105 continuations.106 """107 fp = runCommand(command)108 return sgeOutputFilter(fp)109 38 110 39 def getLrmsInfo(cp): gip/trunk/gip/providers/batch_system.py
r3541 r3545 6 6 sys.path.append(os.path.expandvars("$GIP_LOCATION/lib/python")) 7 7 from gip_common import config, getLogger, cp_get 8 from gip.providers.pbs import main as pbs_main9 from gip.providers.condor import main as condor_main10 8 from gip.providers.sge import main as sge_main 11 9 from gip.providers.lsf import main as lsf_main … … 20 18 log.info("Using job manager %s" % job_manager) 21 19 else: 22 log.error("Job manager not specified!") 23 sys.exit(2) 24 if job_manager == 'condor': 25 condor_main() 26 elif job_manager == 'sge': 20 log.error("Job manager not specified!") 21 sys.exit(2) 22 if job_manager == 'sge': 27 23 sge_main() 28 24 elif job_manager == 'lsf': gip/trunk/test/command_output/commands
r3461 r3545 10 10 condor_status_submitter_xml: condor_status -submitter -xml 11 11 condor_version: condor_version 12 condor_group: condor_config_val GROUP_NAMES13 GROUP_PRIO_FACTOR_group_cdf: condor_config_val GROUP_PRIO_FACTOR_group_cdf14 GROUP_PRIO_FACTOR_group_cms: condor_config_val GROUP_PRIO_FACTOR_group_cms15 GROUP_PRIO_FACTOR_group_cmsprod: condor_config_val GROUP_PRIO_FACTOR_group_cmsprod16 GROUP_PRIO_FACTOR_group_lcgadmin: condor_config_val GROUP_PRIO_FACTOR_group_lcgadmin17 GROUP_PRIO_FACTOR_group_ligo: condor_config_val GROUP_PRIO_FACTOR_group_ligo18 GROUP_QUOTA_group_cdf: condor_config_val GROUP_QUOTA_group_cdf19 GROUP_QUOTA_group_cms: condor_config_val GROUP_QUOTA_group_cms20 GROUP_QUOTA_group_cmsprod: condor_config_val GROUP_QUOTA_group_cmsprod21 GROUP_QUOTA_group_lcgadmin: condor_config_val GROUP_QUOTA_group_lcgadmin22 GROUP_QUOTA_group_ligo: condor_config_val GROUP_QUOTA_group_ligo12 condor_group: condor_config_val -negotiator GROUP_NAMES 13 GROUP_PRIO_FACTOR_group_cdf: condor_config_val -negotiator GROUP_PRIO_FACTOR_group_cdf 14 GROUP_PRIO_FACTOR_group_cms: condor_config_val -negotiator GROUP_PRIO_FACTOR_group_cms 15 GROUP_PRIO_FACTOR_group_cmsprod: condor_config_val -negotiator GROUP_PRIO_FACTOR_group_cmsprod 16 GROUP_PRIO_FACTOR_group_lcgadmin: condor_config_val -negotiator GROUP_PRIO_FACTOR_group_lcgadmin 17 GROUP_PRIO_FACTOR_group_ligo: condor_config_val -negotiator GROUP_PRIO_FACTOR_group_ligo 18 GROUP_QUOTA_group_cdf: condor_config_val -negotiator GROUP_QUOTA_group_cdf 19 GROUP_QUOTA_group_cms: condor_config_val -negotiator GROUP_QUOTA_group_cms 20 GROUP_QUOTA_group_cmsprod: condor_config_val -negotiator GROUP_QUOTA_group_cmsprod 21 GROUP_QUOTA_group_lcgadmin: condor_config_val -negotiator GROUP_QUOTA_group_lcgadmin 22 GROUP_QUOTA_group_ligo: condor_config_val -negotiator GROUP_QUOTA_group_ligo 23 23 COLLECTOR_HOST: condor_config_val COLLECTOR_HOST 24 24 bjobs_u_all_r: bjobs -u all -r gip/trunk/test/command_output/condor_status_submitter_xml_pf2
r1946 r3545 30 30 <a n="WantResAd"><b v="t"/></a> 31 31 <a n="ScheddName"><s>head.unl.edu</s></a> 32 <a n="RunningJobs"><i> 0</i></a>33 <a n="IdleJobs"><i> 2</i></a>32 <a n="RunningJobs"><i>6</i></a> 33 <a n="IdleJobs"><i>0</i></a> 34 34 <a n="HeldJobs"><i>0</i></a> 35 35 <a n="FlockedJobs"><i>0</i></a> gip/trunk/test/condor_test.py
r3448 r3545 9 9 from gip_sets import Set 10 10 from gip_common import config, cp_get 11 from pbs_common import getVoQueues12 11 from gip_ldap import read_ldap 13 12 from gip_testing import runTest, streamHandler … … 47 46 self.assertEquals(entry.glue['CEStateTotalJobs'], '6') 48 47 self.assertEquals(entry.glue['CEStateRunningJobs'], '4') 49 self.assertEquals(entry.glue['CEStateFreeCPUs'], '77') 48 self.assertEquals(entry.glue['CEStateWaitingJobs'], '2') 49 # Free CPUs should be zero as there are waiting jobs 50 self.assertEquals(entry.glue['CEStateFreeCPUs'], '0') 51 self.assertEquals(entry.glue['CEPolicyAssignedJobSlots'], '81') 52 self.assertEquals(entry.glue['CEUniqueID'], \ 53 '%s:2119/jobmanager-condor-default' % ce_name) 54 self.assertEquals(has_ce, True) 55 56 def test_output_pf2(self): 57 """ 58 Test the sample output from prairiefire.unl.edu. Should represent 59 a "simple" Condor setup, no groups or priorities. 60 """ 61 os.environ['GIP_TESTING'] = 'suffix=pf2' 62 path = os.path.expandvars("$GIP_LOCATION/providers/batch_system.py " \ 63 "--config=test_configs/condor_test.conf") 64 fd = os.popen(path) 65 entries = read_ldap(fd) 66 self.assertEquals(fd.close(), None) 67 has_ce = False 68 ce_name = socket.gethostname() 69 for entry in entries: 70 if 'GlueCE' in entry.objectClass: 71 has_ce = True 72 self.assertEquals(entry.glue['CEStateTotalJobs'], '10') 73 self.assertEquals(entry.glue['CEStateRunningJobs'], '10') 74 self.assertEquals(entry.glue['CEStateWaitingJobs'], '0') 75 self.assertEquals(entry.glue['CEStateFreeCPUs'], '71') 50 76 self.assertEquals(entry.glue['CEPolicyAssignedJobSlots'], '81') 51 77 self.assertEquals(entry.glue['CEUniqueID'], \ gip/trunk/test/lsf_test.py
r3454 r3545 34 34 self.failUnless(fd.close() == None) 35 35 36 has_ce = False 36 37 for entry in entries: 37 38 if 'GlueCE' in entry.objectClass: … … 40 41 self.failIf(contact_string.endswith("jobmanager-lsf"), \ 41 42 "Contact string must include the queue.") 43 has_ce = True 44 self.failUnless(has_ce, msg="No GLUE CE has been emitted.") 42 45 43 46 def main(): gip/trunk/test/sge_test.py
r3461 r3545 26 26 path = os.path.expandvars("$GIP_LOCATION/providers/batch_system.py " \ 27 27 "--config=test_configs/pf-sge.conf") 28 print path 28 29 29 fd = os.popen(path) 30 30 fd.read() … … 35 35 path = os.path.expandvars("$GIP_LOCATION/providers/batch_system.py " \ 36 36 "--config=test_configs/pf-sge.conf") 37 print path 37 38 38 fd = os.popen(path) 39 39 entries = read_ldap(fd) 40 40 self.failUnless(fd.close() == None) 41 41 42 has_ce = False 42 43 for entry in entries: 43 44 if 'GlueCE' in entry.objectClass: … … 46 47 self.failIf(contact_string.endswith("jobmanager-sge"), \ 47 48 "Contact string must include the queue.") 49 has_ce = True 50 self.failUnless(has_ce, msg="No GLUE CE object emitted.") 48 51 49 52 def main():
