Back to index

nordugrid-arc-nox  1.1.0~rc6
AREXClient.cpp
Go to the documentation of this file.
00001 // -*- indent-tabs-mode: nil -*-
00002 
00003 #ifdef HAVE_CONFIG_H
00004 #include <config.h>
00005 #endif
00006 
00007 #include <arc/StringConv.h>
00008 #include <arc/UserConfig.h>
00009 #include <arc/client/ClientInterface.h>
00010 #include <arc/delegation/DelegationInterface.h>
00011 #include <arc/infosys/InformationInterface.h>
00012 #include <arc/ws-addressing/WSA.h>
00013 
00014 #include "JobStateARC1.h"
00015 #include "JobStateBES.h"
00016 #include "AREXClient.h"
00017 
00018 #define BES_FACTORY_ACTIONS_BASE_URL "http://schemas.ggf.org/bes/2006/08/bes-factory/BESFactoryPortType/"
00019 
00020 namespace Arc {
00021   const std::string AREXClient::mainStateModel = "nordugrid";
00022 
00023   // TODO: probably worth moving it to common library
00024   // Of course xpath can be used too. But such solution is probably an overkill.
00025   static XMLNode find_xml_node(XMLNode node,
00026                                const std::string& el_name,
00027                                const std::string& attr_name,
00028                                const std::string& attr_value) {
00029     if (MatchXMLName(node, el_name) &&
00030         (((std::string)node.Attribute(attr_name)) == attr_value))
00031       return node;
00032     for (XMLNode cn = node[el_name]; cn; cn = cn[1]) {
00033       XMLNode fn = find_xml_node(cn, el_name, attr_name, attr_value);
00034       if (fn)
00035         return fn;
00036     }
00037     return XMLNode();
00038   }
00039 
00040   Logger AREXClient::logger(Logger::rootLogger, "A-REX-Client");
00041 
00042   static void set_bes_namespaces(NS& ns) {
00043     ns["bes-factory"] = "http://schemas.ggf.org/bes/2006/08/bes-factory";
00044     ns["wsa"] = "http://www.w3.org/2005/08/addressing";
00045     ns["jsdl"] = "http://schemas.ggf.org/jsdl/2005/11/jsdl";
00046     ns["jsdl-posix"] = "http://schemas.ggf.org/jsdl/2005/11/jsdl-posix";
00047     ns["jsdl-hpcpa"] = "http://schemas.ggf.org/jsdl/2006/07/jsdl-hpcpa";
00048   }
00049 
00050   static void set_arex_namespaces(NS& ns) {
00051     ns["a-rex"] = "http://www.nordugrid.org/schemas/a-rex";
00052     ns["glue"] = "http://schemas.ogf.org/glue/2008/05/spec_2.0_d41_r01";
00053     ns["jsdl-arc"] = "http://www.nordugrid.org/ws/schemas/jsdl-arc";
00054     ns["rp"] = "http://docs.oasis-open.org/wsrf/rp-2";
00055     set_bes_namespaces(ns);
00056   }
00057 
00058   AREXClient::AREXClient(const URL& url,
00059                          const MCCConfig& cfg,
00060                          int timeout,
00061                          bool arex_extensions)
00062     : client(NULL),
00063       rurl(url),
00064       arex_enabled(arex_extensions) {
00065 
00066     logger.msg(DEBUG, "Creating an A-REX client");
00067     client = new ClientSOAP(cfg, url, timeout);
00068     if (!client)
00069       logger.msg(VERBOSE, "Unable to create SOAP client used by AREXClient.");
00070     if(arex_enabled) {
00071       set_arex_namespaces(arex_ns);
00072     } else {
00073       set_bes_namespaces(arex_ns);
00074     }
00075   }
00076 
00077   AREXClient::~AREXClient() {
00078     if (client)
00079       delete client;
00080   }
00081 
00082   bool AREXClient::process(PayloadSOAP& req, bool delegate, XMLNode& response) {
00083     if (!client) {
00084       logger.msg(VERBOSE, "AREXClient was not created properly."); // Should not happen. Happens if client = null (out of memory?)
00085       return false;
00086     }
00087 
00088     logger.msg(VERBOSE, "Processing a %s request", req.Child(0).FullName());
00089 
00090     if (delegate) {
00091       // Try to figure out which credentials are used
00092       // TODO: Method used is unstable beacuse it assumes some predefined
00093       // structure of configuration file. Maybe there should be some
00094       // special methods of ClientTCP class introduced.
00095       std::string deleg_cert;
00096       std::string deleg_key;
00097 
00098       client->Load(); // Make sure chain is ready
00099       XMLNode tls_cfg = find_xml_node((client->GetConfig())["Chain"],
00100                                       "Component", "name", "tls.client");
00101       if (tls_cfg) {
00102         deleg_cert = (std::string)(tls_cfg["ProxyPath"]);
00103         if (deleg_cert.empty()) {
00104           deleg_cert = (std::string)(tls_cfg["CertificatePath"]);
00105           deleg_key = (std::string)(tls_cfg["KeyPath"]);
00106         }
00107         else
00108           deleg_key = deleg_cert;
00109       }
00110       if (deleg_cert.empty() || deleg_key.empty()) {
00111         logger.msg(VERBOSE, "Failed locating delegation credentials in chain configuration");
00112         return false;
00113       }
00114 
00115       DelegationProviderSOAP deleg(deleg_cert, deleg_key);
00116       logger.msg(VERBOSE, "Initiating delegation procedure");
00117       if (!deleg.DelegateCredentialsInit(*(client->GetEntry()),
00118                                          &(client->GetContext()))) {
00119         logger.msg(VERBOSE, "Failed to initiate delegation credentials");
00120         return false;
00121       }
00122       XMLNode op = req.Child(0);
00123       deleg.DelegatedToken(op);
00124     }
00125 
00126     WSAHeader header(req);
00127     header.To(rurl.str());
00128     PayloadSOAP* resp = NULL;
00129     if (!client->process(header.Action(), &req, &resp)) {
00130       logger.msg(VERBOSE, "%s request failed", action);
00131       return false;
00132     }
00133 
00134     if (resp == NULL) {
00135       logger.msg(VERBOSE, "No response from %s", rurl.str());
00136       return false;
00137     }
00138 
00139     if (resp->IsFault()) {
00140       logger.msg(VERBOSE, "%s request to %s failed with response: %s", action, rurl.str(), resp->Fault()->Reason());
00141       std::string s;
00142       resp->GetXML(s);
00143       logger.msg(DEBUG, "XML response: %s", s);
00144       delete resp;
00145       return false;
00146     }
00147 
00148     if (!(*resp)[action + "Response"]) {
00149       logger.msg(VERBOSE, "%s request to %s failed. Empty response.", action, rurl.str());
00150       delete resp;
00151       return false;
00152     }
00153 
00154     (*resp)[action + "Response"].New(response);
00155     delete resp;
00156     return true;
00157   }
00158 
00159   bool AREXClient::submit(const std::string& jobdesc, std::string& jobid,
00160                           bool delegate) {
00161     action = "CreateActivity";
00162     logger.msg(VERBOSE, "Creating and sending submit request to %s", rurl.str());
00163 
00164     // Create job request
00165     /*
00166        bes-factory:CreateActivity
00167          bes-factory:ActivityDocument
00168            jsdl:JobDefinition
00169      */
00170 
00171     PayloadSOAP req(arex_ns);
00172     XMLNode op = req.NewChild("bes-factory:" + action);
00173     XMLNode act_doc = op.NewChild("bes-factory:ActivityDocument");
00174     WSAHeader(req).Action(BES_FACTORY_ACTIONS_BASE_URL + action);
00175     act_doc.NewChild(XMLNode(jobdesc));
00176     act_doc.Child(0).Namespaces(arex_ns); // Unify namespaces
00177 
00178     logger.msg(DEBUG, "Job description to be sent: %s", jobdesc);
00179 
00180     XMLNode response;
00181     if (!process(req, delegate, response))
00182       return false;
00183 
00184     XMLNode xmlJobId;
00185     response["ActivityIdentifier"].New(xmlJobId);
00186     xmlJobId.GetDoc(jobid);
00187     return true;
00188   }
00189 
00190   bool AREXClient::stat(const std::string& jobid, Job& job) {
00191     std::string faultstring;
00192     logger.msg(VERBOSE, "Creating and sending job information query request to %s", rurl.str());
00193 
00194     PayloadSOAP req(arex_ns);
00195     if(arex_enabled) {
00196       // TODO: use wsrf classes
00197       // AREX service
00198       action = "QueryResourceProperties";
00199       std::string xpathquery = "//glue:Services/glue:ComputingService/glue:ComputingEndpoint/glue:ComputingActivities/glue:ComputingActivity/glue:ID[contains(.,'" + (std::string)(XMLNode(jobid)["ReferenceParameters"]["JobID"]) + "')]/..";
00200       req = *InformationRequest(XMLNode("<XPathQuery>" + xpathquery + "</XPathQuery>")).SOAP();
00201     } else {
00202       // Simple BES service
00203       // GetActivityStatuses
00204       //  ActivityIdentifier
00205       action = "GetActivityStatuses";
00206       XMLNode jobref =
00207         req.NewChild("bes-factory:" + action).
00208         NewChild(XMLNode(jobid));
00209       jobref.Child(0).Namespaces(arex_ns); // Unify namespaces
00210       WSAHeader(req).Action(BES_FACTORY_ACTIONS_BASE_URL + action);
00211     }
00212 
00213     XMLNode response;
00214     if (!process(req, false, response))
00215       return false;
00216 
00217     if(arex_enabled) {
00218       // Fetch the proper state.
00219       for (int i = 0; response["ComputingActivity"]["State"][i]; i++) {
00220         const std::string rawState = (std::string)response["ComputingActivity"]["State"][i];
00221         const std::size_t pos = rawState.find_first_of(':');
00222         if (pos == std::string::npos) {
00223           logger.msg(VERBOSE, "Found malformed job state string: %s", rawState);
00224           continue;
00225         }
00226         const std::string model = rawState.substr(0, pos);
00227         const std::string state = rawState.substr(pos + 1);
00228         if (model == mainStateModel)
00229           job.State = JobStateARC1(state);
00230         job.AuxStates[model] = state;
00231       }
00232     } else {
00233       XMLNode activity = response["Response"]["ActivityStatus"];
00234       if(activity) {
00235         NS ns("a-rex","http://www.nordugrid.org/schemas/a-rex");
00236         activity.Namespaces(ns);
00237         std::string state = activity.Attribute("state");
00238         if(!state.empty()) {
00239           job.State = JobStateBES(state);
00240           XMLNode nstate = activity["a-rex:State"];
00241           std::string nstates;
00242           for(;nstate;++nstate) {
00243             if(nstates.empty()) {
00244               nstates = (std::string)nstate;
00245             } else {
00246               nstates = (std::string)nstate + ":" + nstates;
00247             }
00248           }
00249           if(!nstates.empty()) {
00250             job.State = JobStateARC1(nstates);
00251             job.AuxStates["nordugrid"] = nstates;
00252             job.AuxStates["bes"] = state;
00253           }
00254         }
00255       }
00256     }
00257 
00258     if (!job.State) {
00259       if(arex_enabled) {
00260         // If failed to fetch through Glue2 try through BES
00261         arex_enabled = false;
00262         bool r = stat(jobid,job);
00263         arex_enabled = true;
00264         return r;
00265       } else {
00266         logger.msg(VERBOSE, "Unable to retrieve status of job (%s)", job.JobID.str());
00267       }
00268       return false;
00269     }
00270     if(!arex_enabled) return true;
00271 
00272     // The following elements (except the 'State' element) seem to be published by A-REX, for more info see the ARC1ClusterInfo.pm script.
00273     XMLNode jobNode = response["ComputingActivity"];
00274     if (jobNode["CreationTime"])
00275       job.CreationTime = Time((std::string)jobNode["CreationTime"]);
00276     if (jobNode["Type"])
00277       job.Type = (std::string)jobNode["Type"];
00278     if (jobNode["IDFromEndpoint"])
00279       job.IDFromEndpoint = URL((std::string)jobNode["IDFromEndpoint"]);
00280     if (jobNode["LocalIDFromManager"])
00281       job.LocalIDFromManager = (std::string)jobNode["LocalIDFromManager"];
00282     if (jobNode["Name"])
00283       job.Name = (std::string)jobNode["Name"];
00284     if (jobNode["JobDescription"])
00285       job.JobDescription = (std::string)jobNode["JobDescription"];
00286     if (jobNode["RestartState"])
00287       job.RestartState = (std::string)jobNode["RestartState"];
00288     if (jobNode["ExitCode"])
00289       job.ExitCode = stringtoi(jobNode["ExitCode"]);
00290     if (jobNode["ComputingManagerExitCode"])
00291       job.ComputingManagerExitCode = (std::string)jobNode["ComputingManagerExitCode"];
00292     for (XMLNode errorXML = jobNode["Error"]; errorXML; ++errorXML)
00293       job.Error.push_back((std::string)errorXML);
00294     if (jobNode["WaitingPosition"])
00295       job.WaitingPosition = stringtoi((std::string)jobNode["WaitingPosition"]);
00296     if (jobNode["Owner"])
00297       job.Owner = (std::string)jobNode["Owner"];
00298     if (jobNode["LocalOwner"])
00299       job.LocalOwner = (std::string)jobNode["LocalOwner"];
00300     if (jobNode["RequestedTotalWallTime"])
00301       job.RequestedTotalWallTime = Period((std::string)jobNode["RequestedTotalWallTime"]);
00302     if (jobNode["RequestedTotalCPUTime"])
00303       job.RequestedTotalCPUTime = Period((std::string)jobNode["RequestedTotalCPUTime"]);
00304     for (XMLNode appEnvXML = jobNode["RequestedApplicationEnvironment"]; appEnvXML; ++appEnvXML)
00305       job.RequestedApplicationEnvironment.push_back((std::string)appEnvXML);
00306     if (jobNode["RequestedSlots"])
00307       job.RequestedSlots = stringtoi((std::string)jobNode["RequestedSlots"]);
00308     if (jobNode["LogDir"])
00309       job.LogDir = (std::string)jobNode["LogDir"];
00310     if (jobNode["StdIn"])
00311       job.StdIn = (std::string)jobNode["StdIn"];
00312     if (jobNode["StdOut"])
00313       job.StdOut = (std::string)jobNode["StdOut"];
00314     if (jobNode["StdErr"])
00315       job.StdErr = (std::string)jobNode["StdErr"];
00316     for (XMLNode exeNodeXML = jobNode["ExecutionNode"]; exeNodeXML; ++exeNodeXML)
00317       job.ExecutionNode.push_back((std::string)exeNodeXML);
00318     if (jobNode["Queue"])
00319       job.Queue = (std::string)jobNode["Queue"];
00320     if (jobNode["UsedTotalWallTime"])
00321       job.UsedTotalWallTime = Period((std::string)jobNode["UsedTotalWallTime"]);
00322     if (jobNode["UsedTotalCPUTime"])
00323       job.UsedTotalCPUTime = Period((std::string)jobNode["UsedTotalCPUTime"]);
00324     if (jobNode["UsedMainMemory"])
00325       job.UsedMainMemory = stringtoi((std::string)jobNode["UsedMainMemory"]);
00326     if (jobNode["SubmissionTime"])
00327       job.SubmissionTime = Time((std::string)jobNode["SubmissionTime"]);
00328     if (jobNode["EndTime"])
00329       job.EndTime = (std::string)jobNode["EndTime"];
00330     if (jobNode["WorkingAreaEraseTime"])
00331       job.WorkingAreaEraseTime = Time((std::string)jobNode["WorkingAreaEraseTime"]);
00332     if (jobNode["ProxyExpirationTime"])
00333       job.ProxyExpirationTime = Time((std::string)jobNode["ProxyExpirationTime"]);
00334     if (jobNode["SubmissionHost"])
00335       job.SubmissionHost = (std::string)jobNode["SubmissionHost"];
00336     if (jobNode["SubmissionClientName"])
00337       job.SubmissionClientName = (std::string)jobNode["SubmissionClientName"];
00338 
00339     // The following elements do not seem to be published by A-REX (they are not in ARC1ClusterInfo.om). They are kept here for consistency.
00340     if (jobNode["ComputingManagerEndTime"])
00341       job.ComputingManagerEndTime = Time((std::string)jobNode["ComputingManagerEndTime"]);
00342     if (jobNode["ComputingManagerSubmissionTime"])
00343       job.ComputingManagerSubmissionTime = Time((std::string)jobNode["ComputingManagerSubmissionTime"]);
00344     if (jobNode["LocalSubmissionTime"])
00345       job.LocalSubmissionTime = Time((std::string)jobNode["LocalSubmissionTime"]);
00346     if (jobNode["StartTime"])
00347       job.StartTime = Time((std::string)jobNode["StartTime"]);
00348 
00349     return true;
00350   }
00351 
00352   bool AREXClient::sstat(XMLNode& response) {
00353     if(!arex_enabled) return false;
00354 
00355     action = "QueryResourceProperties";
00356     logger.msg(VERBOSE, "Creating and sending service information query request to %s", rurl.str());
00357 
00358     PayloadSOAP req(*InformationRequest(XMLNode("<XPathQuery>//glue:Services/glue:ComputingService</XPathQuery>")).SOAP());
00359 
00360     if (!process(req, false, response))
00361       return false;
00362 
00363     return true;
00364   }
00365 
00366   bool AREXClient::listServicesFromISIS(std::list< std::pair<URL, ServiceType> >& services) {
00367     if(!arex_enabled) return false;
00368 
00369     action = "Query";
00370     logger.msg(VERBOSE, "Creating and sending ISIS information query request to %s", rurl.str());
00371 
00372     PayloadSOAP req(NS("isis", "http://www.nordugrid.org/schemas/isis/2007/06"));
00373     req.NewChild("isis:" + action).NewChild("isis:QueryString") = "/RegEntry/SrcAdv[Type=\"org.nordugrid.execution.arex\"]";
00374     WSAHeader(req).Action("http://www.nordugrid.org/schemas/isis/2007/06/Query/QueryRequest");
00375 
00376     XMLNode response;
00377     if (!process(req, false, response))
00378       return false;
00379 
00380     if (XMLNode n = response["RegEntry"])
00381       for (; n; ++n) {
00382         if ((std::string)n["SrcAdv"]["Type"] == "org.nordugrid.execution.arex") {
00383           //This check is right now superfluos but in the future a wider query might be used
00384           services.push_back(std::pair<URL, ServiceType>(URL((std::string)n["SrcAdv"]["EPR"]["Address"]), COMPUTING));
00385         }
00386         else
00387           logger.msg(DEBUG, "Service %s of type %s ignored", (std::string)n["MetaSrcAdv"]["ServiceID"], (std::string)n["SrcAdv"]["Type"]);
00388       }
00389     else
00390       logger.msg(VERBOSE, "No execution services registered in the index service");
00391     return true;
00392   }
00393 
00394   bool AREXClient::kill(const std::string& jobid) {
00395     action = "TerminateActivities";
00396     logger.msg(VERBOSE, "Creating and sending terminate request to %s", rurl.str());
00397 
00398     PayloadSOAP req(arex_ns);
00399     XMLNode jobref = req.NewChild("bes-factory:" + action).NewChild(XMLNode(jobid));
00400     WSAHeader(req).Action(BES_FACTORY_ACTIONS_BASE_URL + action);
00401 
00402     XMLNode response;
00403     if (!process(req, false, response))
00404       return false;
00405 
00406     if ((std::string)response["Response"]["Terminated"] != "true") {
00407       logger.msg(ERROR, "Job termination failed");
00408       return false;
00409     }
00410 
00411     return true;
00412   }
00413 
00414   bool AREXClient::clean(const std::string& jobid) {
00415     if(!arex_enabled) return false;
00416 
00417     action = "ChangeActivityStatus";
00418     logger.msg(VERBOSE, "Creating and sending clean request to %s", rurl.str());
00419 
00420     PayloadSOAP req(arex_ns);
00421     XMLNode op = req.NewChild("a-rex:" + action);
00422     op.NewChild(XMLNode(jobid));
00423     XMLNode jobstate = op.NewChild("a-rex:NewStatus");
00424     jobstate.NewAttribute("bes-factory:state") = "Finished";
00425     jobstate.NewChild("a-rex:state") = "Deleted";
00426 
00427     // Send clean request
00428     XMLNode response;
00429     if (!process(req, false, response))
00430       return false;
00431 
00432 /*
00433  * It is not clear how (or if) the response should be interpreted.
00434  * Currently response contains status of job before invoking requst. It is
00435  * unclear if this is the desired behaviour.
00436  * See trunk/src/services/a-rex/change_activity_status.cpp
00437     ????if ((std::string)response["NewStatus"]["state"] != "Deleted") {????
00438       logger.msg(VERBOSE, "Job cleaning failed: Wrong response???");
00439       return false;
00440     }
00441 */
00442 
00443     return true;
00444   }
00445 
00446   bool AREXClient::getdesc(const std::string& jobid, std::string& jobdesc) {
00447     action = "GetActivityDocuments";
00448     logger.msg(VERBOSE, "Creating and sending job description retrieval request to %s", rurl.str());
00449 
00450     PayloadSOAP req(arex_ns);
00451     req.NewChild("bes-factory:" + action).NewChild(XMLNode(jobid));
00452     WSAHeader(req).Action(BES_FACTORY_ACTIONS_BASE_URL + action);
00453 
00454     XMLNode response;
00455     if (!process(req, false, response))
00456       return false;
00457 
00458     XMLNode xmlJobDesc;
00459     response["Response"]["JobDefinition"].New(xmlJobDesc);
00460     xmlJobDesc.GetDoc(jobdesc);
00461     return true;
00462   }
00463 
00464   bool AREXClient::migrate(const std::string& jobid, const std::string& jobdesc, bool forcemigration, std::string& newjobid, bool delegate) {
00465     if(!arex_enabled) return false;
00466 
00467     action = "MigrateActivity";
00468     logger.msg(VERBOSE, "Creating and sending job migrate request to %s", rurl.str());
00469 
00470     // Create migrate request
00471     /*
00472        bes-factory:MigrateActivity
00473         bes-factory:ActivityIdentifier
00474         bes-factory:ActivityDocument
00475           jsdl:JobDefinition
00476      */
00477 
00478     PayloadSOAP req(arex_ns);
00479     XMLNode op = req.NewChild("a-rex:" + action);
00480     XMLNode act_doc = op.NewChild("bes-factory:ActivityDocument");
00481     op.NewChild(XMLNode(jobid));
00482     op.NewChild("a-rex:ForceMigration") = (forcemigration ? "true" : "false");
00483     act_doc.NewChild(XMLNode(jobdesc));
00484     act_doc.Child(0).Namespaces(arex_ns); // Unify namespaces
00485 
00486     logger.msg(DEBUG, "Job description to be sent: %s", jobdesc);
00487 
00488     XMLNode response;
00489     if (!process(req, delegate, response))
00490       return false;
00491 
00492     XMLNode xmlNewJobId;
00493     response["ActivityIdentifier"].New(xmlNewJobId);
00494     xmlNewJobId.GetDoc(newjobid);
00495     return true;
00496   }
00497 
00498   bool AREXClient::resume(const std::string& jobid) {
00499     if(!arex_enabled) return false;
00500 
00501     action = "ChangeActivityStatus";
00502     logger.msg(VERBOSE, "Creating and sending job resume request to %s", rurl.str());
00503 
00504     bool delegate = true;
00505     PayloadSOAP req(arex_ns);
00506     XMLNode op = req.NewChild("a-rex:" + action);
00507     op.NewChild(XMLNode(jobid));
00508     XMLNode jobstate = op.NewChild("a-rex:NewStatus");
00509     jobstate.NewAttribute("bes-factory:state") = "Running";
00510     // Not supporting resume into user-defined state
00511     jobstate.NewChild("a-rex:state") = "";
00512 
00513     XMLNode response;
00514     if (!process(req, true, response))
00515       return false;
00516 
00517 /*
00518  * It is not clear how (or if) the response should be interpreted.
00519  * Currently response contains status of job before invoking requst. It is
00520  * unclear if this is the desired behaviour.
00521  * See trunk/src/services/a-rex/change_activity_status.cpp
00522     ????if ((std::string)response["NewStatus"]["state"] != "Running") {????
00523       logger.msg(VERBOSE, "Job resuming failed: Wrong response???");
00524       return false;
00525     }
00526 */
00527 
00528     return true;
00529   }
00530 
00531   void AREXClient::createActivityIdentifier(const URL& jobid, std::string& activityIdentifier) {
00532     PathIterator pi(jobid.Path(), true);
00533     URL url(jobid);
00534     url.ChangePath(*pi);
00535     NS ns;
00536     ns["a-rex"] = "http://www.nordugrid.org/schemas/a-rex";
00537     ns["bes-factory"] = "http://schemas.ggf.org/bes/2006/08/bes-factory";
00538     ns["wsa"] = "http://www.w3.org/2005/08/addressing";
00539     ns["jsdl"] = "http://schemas.ggf.org/jsdl/2005/11/jsdl";
00540     ns["jsdl-posix"] = "http://schemas.ggf.org/jsdl/2005/11/jsdl-posix";
00541     ns["jsdl-arc"] = "http://www.nordugrid.org/ws/schemas/jsdl-arc";
00542     ns["jsdl-hpcpa"] = "http://schemas.ggf.org/jsdl/2006/07/jsdl-hpcpa";
00543     XMLNode id(ns, "ActivityIdentifier");
00544     id.NewChild("wsa:Address") = url.str();
00545     id.NewChild("wsa:ReferenceParameters").NewChild("a-rex:JobID") = pi.Rest();
00546     id.GetXML(activityIdentifier);
00547   }
00548 }