{section: Overview} All of the HTCondor daemons can talk soap as detailed in {link: http://research.cs.wisc.edu/condor/manual/v7.6/4_5Application_Program.html#SECTION00551000000000000000 the 7.6 manual} For a very clear and concise intro to SOAP messaging, see http://www.soapware.org/bdg A good resource is a 2006 HTCondor Week Powerpoint presentation that gives an overview using HTCondor's SOAP interface and some code snippets in Java: {link: http://www.cs.wisc.edu/condor/CondorWeek2006/presentations/farrellee_tannenba_APIs.ppt} Barebones netcat example: (assumes somehost.example.com:9618 is your soap-enabled collector) {code} [griswold@nmi-s005 build]$ nc somehost.example.com 9618 < POST / HTTP/1.1 > User-Agent: whatever > Content-Type: text/xml; charset=utf-8 > SOAPAction: "" > > > xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" > xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" > xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xmlns:xsd="http://www.w3.org/2001/XMLSchema" > xmlns:ns="urn:condor"> > > > > > EOF HTTP/1.1 200 OK Server: gSOAP/2.7 Content-Type: text/xml; charset=utf-8 Content-Length: 547 Connection: close SUCCESS $CondorVersion: 7.1.4 Nov 10 2008 BuildID: 113473 $ {endcode} Valid function calls such as getVersionString in the above example can be found in the different wsdl files provided with HTCondor. HTCondor ships with wsdl files for all daemons and they are located in lib/webservice in the binary releases. Also note the use of the namespace urn:condor, you may have to specify this in your client library. Each daemon provides a separate SOAP server. The only HTCondor daemon that runs on a static port by default is the collector (port 9618). Therefore, in order to connect to other daemons in your pool (whose ports are randomized), you have two options: 1: Configure any daemons you care about to have static port configurations. In the case of the schedd, this would be done using something like SCHEDD_ARGS = -p 8080. This can simplify client code. You can further simplify client code by updating the location parameter found at the bottom of any wsdl files you make use of. 1: Don't worry about static ports and discover all daemon addresses in your pool dynamically from the collector. This is the route chosen in the below tutorial and in birdbath (http://www.cs.wisc.edu/condor/birdbath/) {section: Tutorial} This is a minimalist example to get you going. It is tested with {link: https://fedorahosted.org/suds/ suds-0.3.5} and python 2.4 (should be python 2.3 compatible). It should be helpful to get going with other languages, as well. {subsection: HTCondor Configuration} Note that **this is a really insecure configuration**, especially for public hosts. See {link: http://www.cs.wisc.edu/condor/manual/v7.3/4_5Application_Program.html#SECTION00551400000000000000 the manual} for more info. Also note that we are exposing the wsdl files on each server using WEB_ROOT_DIR. This is especially important in mixed HTCondor version environments since it ties the wsdl to the server it is used on. Add the below to all HTCondor configurations {code} ENABLE_SOAP = TRUE ENABLE_WEB_SERVER = TRUE WEB_ROOT_DIR=$(RELEASE_DIR)/lib/webservice ALLOW_SOAP = */* QUEUE_ALL_USERS_TRUSTED = TRUE {endcode} Note: In order to process and receive API calls, the machine acting as the SOAP gateway must have the ability to submit jobs. A dedicated central manager will be unable to process the request. {subsection: Barebones suds client with xml debugging output} This is equivalent to the netcat example above. It connects to the collector and queries the version. {code} #!/usr/bin/env python from suds.client import Client import logging logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) collector_url = 'http://somehost.example.com:9618/' if __name__ == '__main__': url = '%scondorCollector.wsdl' % collector_url collector = Client(url, cache=None, location=collector_url) print collector.service.getVersionString() {endcode} Notes: 1: We are using the (currently) undocumented "location" suds.client.Client argument to override any location defined in the WSDL. {subsection: Slightly more advanced client to find a schedd} This thing will pick a random schedd among those with TotalRunningJobs < 5 and print its suds structure. Should add transactional job submission to this example. {code} #!/usr/bin/env python from suds.client import Client import urllib import random import logging import re collector_url = 'http://nighthawk.cs.wisc.edu:9618/' def classad_dict(ad): native = {} attrs = ad[0] for attr in attrs: native[attr['name']] = attr['value'] return native condor_addr_re = re.compile('<(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(?P\d+)>') def find_schedd(collector, constraint=''): ''' Pick a schedd matching the given, optional constraint raises ValueError if no matching schedd can be found ''' schedds = collector.service.queryScheddAds(constraint) try: schedd = random.choice(schedds[0]) except IndexError: raise ValueError('no schedds found') print schedds schedd_ad = classad_dict(schedd) m = condor_addr_re.match(schedd_ad['ScheddIpAddr']) assert m, 'bad address %s' % schedd_ad['ScheddIpAddr'] url = 'http://%(ip)s:%(port)s/' % m.groupdict() schedd = Client('%scondorSchedd.wsdl' % url, cache=None, location=url) return schedd if __name__ == '__main__': url = '%s/condorCollector.wsdl' % collector_url collector = Client(url, cache=None, location=collector_url) schedd = find_schedd(collector, 'TotalRunningJobs < 5') print schedd {endcode} Notes: 1: The HTCondor classad structures are a little strange. Instead being a mapping of string keys to native soap types, they are an array of mappings that look like this: {code}(item){ name = "CondorVersion" type = "STRING-ATTR" value = "$CondorVersion: 7.3.1 May 19 2009 BuildID: 154007 $" } {endcode} Consider adding conversion to native types in your language of choice (the classad_dict function is really naive). 1: The structures seem slightly too deep. For example, in order to access the ad listing for the first schedd returned from queryScheddAds, you must index result[0][0][0]. The result structure looks like this: {code} (ClassAdStructArray){ item[] = (item){ item[] = (item){ name = "MyType" type = "STRING-ATTR" value = "Scheduler" },... etc }, } {endcode} {section: Additional Examples} {link: http://spinningmatt.wordpress.com/2009/11/02/submitting-a-workflow-to-condor-via-soap-using-java/ This blog post} explains how to submit a DAGman job to HTCondor using the SOAP interface from Java. Here are a few additional examples of using the HTCondor SOAP interface, this time in Perl. These examples assume that the _condor_schedd_ has been configured to run on well-known port 8080 as described above. Note that you should be using a very recent version of the SOAP::Lite module. 0.60 won't work, 0.712 will. {subsection: Get Version String in Perl} {code} use SOAP::Lite ; my $soap = SOAP::Lite->new( proxy => 'http://localhost:8080/soap', default_ns => 'urn:condor' ); my $som = $soap->call( "getVersionString" ); die $som->fault->{ faultstring } if ($som->fault); my %result = %{$som->result}; print "$result{'message'}\n"; $som = $soap->call( "getPlatformString" ); die $som->fault->{ faultstring } if ($som->fault); %result = %{$som->result}; print "$result{'message'}\n"; {endcode} {subsection: Get Job Queue in Perl} This code example will produce output similar to _condor_q -l_. An optional command line argument can pass a constraint, similar to the _-constraint_ option with condor_q. {code} use SOAP::Lite ; # # Create a connection to the schedd # my $soap = SOAP::Lite->new( proxy => 'http://localhost:8080/soap', default_ns => 'urn:condor' ); # # Invoke getJobAds() # my $som = $soap->call( "getJobAds", SOAP::Data->name('constraint')->value( $ARGV[0] || 'TRUE'), ); die $som->fault->{ faultstring } if ($som->fault); my %result = %{$som->result}; # # For each ad returned, print all attributes # my @ads; if( ref ($result{'classAdArray'}{'item'}) eq 'HASH') { @ads = $result{'classAdArray'}{'item'}; } else { @ads = @{$result{'classAdArray'}{'item'}}; } foreach my $ad_ref (@ads) { my @ad = @{$ad_ref->{'item'}}; foreach my $attr_ref (@ad) { my %attr = %$attr_ref; print " $attr{'name'} = $attr{'value'} \n"; } print "===============================\n"; } {endcode}