Pārlūkot izejas kodu

FIXED: Bug81658 Add validation checks for mandatory settings

1. Validation errors/warnings will not prevent the users from saving/using the environment
2. When a new instance is added on a computer and a corresponding dafilesrv instance does not
exist on that computer, user is prompted with a message that will add one for him. User can accept or decline.
3. If an instance of dafilesrv does not exist on any node that has other hpcc-system components
it is flagged as an validation error
4. All validation errors/warnings are shown at the same time rather than one at a time
5. If localThor is set to true and all the thor slaves and the master are not on the same node,
it is flagged as an validation error
6. FTSlave is started on demand and should not be generated via wizard nor should it be present in the default environment.xml

Signed-off-by: Sridhar Meda <sridhar.meda@lexisnexis.com>
Sridhar Meda 13 gadi atpakaļ
vecāks
revīzija
3a137cc1eb

+ 50 - 20
deployment/configgen/configengcallback.hpp

@@ -66,7 +66,7 @@ class CConfigEngCallback: public CInterface, implements IDeploymentCallback
         StringBuffer sbErrCode;
 
         if (task->getErrorCode() > 0)
-            sbErrCode.appendlong(task->getErrorCode());
+          sbErrCode.appendlong(task->getErrorCode());
         
         if (sbErr.length() == 0 && sbWarnings.length() == 0 && sbErrCode.length() == 0)
             sb.append("Result: Success\n");
@@ -122,29 +122,56 @@ class CConfigEngCallback: public CInterface, implements IDeploymentCallback
         IException* e, const char* szMessage=NULL, const char* szCaption=NULL,
         IDeployTask* pTask = NULL )
     {
-        if (m_abortOnException) 
-        {
-            StringBuffer sb;
+      StringBuffer sb;
 
-            if (processType)
-                sb.append("Process type: ").append(processType).append("\n");
-            if (process)
-                sb.append("Component: ").append(process).append("\n");
-            if (instance)
-                sb.append("Instance: ").append(instance).append("\n");
-            if (szMessage)
-                sb.append("Message: ").append(szMessage).append("\n");
+      if (m_errIdx > 1)
+        sb.append("\n");
+
+      sb.appendf("Error %d:\n", m_errIdx++);
 
-            if (!m_abort)
-                m_abort = true;
+      if (szMessage)
+      {
+        if (processType)
+          sb.append("Component type: ").append(processType).append("\n");
+        if (process)
+          sb.append("Component Name: ").append(process).append("\n");
 
-            if (szMessage)
-                m_sbExMsg.append(sb);
+        StringBuffer errMsg(szMessage);
+        String str(errMsg.trim());
+
+        if (str.lastIndexOf('[') > 0)
+        {
+          errMsg.clear();
+          String* sub1 = str.substring(str.lastIndexOf('[') + 1, str.length() - 1);
 
-            throw MakeStringException(0, "%s", m_sbExMsg.str());
+          if (sub1)
+          {
+            String* sub2 = sub1->substring(sub1->indexOf(':') + 1, sub1->length());
+
+            if (sub2)
+            {
+              StringBuffer sb2(*sub2);
+              sb2.trim();
+              sb2.replaceString("  ", " ");
+              errMsg.append(sb2.str());
+              delete sub2;
+            }
+
+            delete sub1;
+          }
         }
-        
-        return true;
+
+        sb.append(errMsg).append("\n");
+        m_sbExMsg.append(sb);
+      }
+
+      if (m_abortOnException)
+      {
+        m_abort = true;
+        throw MakeStringException(0, "%s", m_sbExMsg.str());
+      }
+
+      return true;
     }
     virtual IEnvDeploymentEngine* getEnvDeploymentEngine()const{return NULL;}
     virtual void* getWindowHandle() const{return NULL;}
@@ -156,8 +183,11 @@ private:
     bool m_abortOnException;
     bool m_abort;
     StringBuffer m_sbExMsg;
+    unsigned m_errIdx;
 public: // IDeploymentCallback
     IMPLEMENT_IINTERFACE;
-    CConfigEngCallback(bool verbose = false, bool abortOnException = false){m_verbose = verbose;m_abortOnException=abortOnException;m_abort=false;}
+    CConfigEngCallback(bool verbose = false, bool abortOnException = false){m_verbose = verbose;m_abortOnException=abortOnException;m_abort=false;m_errIdx=1;}
+    virtual const char* getErrorMsg() { return m_sbExMsg.str(); }
+    virtual unsigned getErrorCount() { return m_errIdx;}
 };
 #endif

+ 12 - 8
deployment/deploy/DeploymentEngine.cpp

@@ -52,7 +52,8 @@ CDeploymentEngine::CDeploymentEngine(IEnvDeploymentEngine& envDepEngine,
                                      m_startable(unknown),
                                      m_stoppable(unknown),
                                      m_createIni(createIni),
-                                     m_curInstance(NULL)
+                                     m_curInstance(NULL),
+                                     m_instanceCheck(true)
 {
     m_pCallback.set(&callback);
     m_installFiles.setDeploymentEngine(*this);
@@ -466,14 +467,17 @@ void CDeploymentEngine::check()
     if (m_instances.empty())
         throw MakeStringException(0, "Process %s has no instances defined.  Nothing to do!", m_name.get());
     
-    ForEachItemIn(idx, m_instances)
+    if (m_instanceCheck)
     {
-        checkAbort();
-        
-        IPropertyTree& instance = m_instances.item(idx);
-        m_curInstance = instance.queryProp("@name");
-        
-        checkInstance(instance);
+      ForEachItemIn(idx, m_instances)
+      {
+          checkAbort();
+
+          IPropertyTree& instance = m_instances.item(idx);
+          m_curInstance = instance.queryProp("@name");
+
+          checkInstance(instance);
+      }
     }
     m_curInstance = NULL;
     clearSSHVars();

+ 1 - 0
deployment/deploy/DeploymentEngine.hpp

@@ -346,6 +346,7 @@ protected:
     bool       m_createIni;
     bool       m_abort;
     bool m_useSSHIfDefined;
+    bool m_instanceCheck;
 };
 
 

+ 30 - 3
deployment/deploy/deploy.cpp

@@ -52,6 +52,7 @@ public:
         m_bInteractiveMode(true)
     {
         m_pCallback.set(&callback);
+        m_validateAllXsl.clear().append("validateAll.xsl");
 
     if (pSelectedComponents)
     {
@@ -258,8 +259,7 @@ public:
         Owned<IXslFunction>  externalFunction;
         externalFunction.setown(m_transform->createExternalFunction("validationMessage", validationMessageFromXSLT));
         m_transform->setExternalFunction(SEISINT_NAMESPACE, externalFunction.get(), true);
-        
-        m_transform->loadXslFromFile("validateAll.xsl");
+        m_transform->loadXslFromFile(m_validateAllXsl.str());
         m_transform->setUserData(this);
         
         try
@@ -561,7 +561,7 @@ public:
             pEnvDepEngine->m_nValidationErrors++;
 
             if (!compType || !compName)//if this error is not being reported under a particular component in tree
-                pEnvDepEngine->m_sValidationErrors.append(msg);
+                pEnvDepEngine->m_sValidationErrors.append(msg).append("\n");
         }
         else if (!strnicmp(msgType, "warn", 4))
             statusType = STATUS_WARN;
@@ -912,6 +912,7 @@ protected:
     bool m_bInteractiveMode;
     StringBuffer m_sSshUserid;
     StringBuffer m_sSshPassword;
+    StringBuffer m_validateAllXsl;
     unsigned int m_nValidationErrors;
     StringBuffer m_sValidationErrors;
     StringArray  m_tempFiles;
@@ -939,6 +940,12 @@ public:
     m_compType(compType),
     m_hostIpAddr(ipAddr)
   {
+    m_validateAllXsl.clear().append(inputDir);
+    if (m_validateAllXsl.length() > 0 &&
+        m_validateAllXsl.charAt(m_validateAllXsl.length() - 1) != PATHSEPCHAR)
+      m_validateAllXsl.append(PATHSEPCHAR);
+    m_validateAllXsl.append("validateAll.xsl");
+
     Owned<IPropertyTree> pSelComps;
     if (!pSelectedComponents)
     {
@@ -1027,6 +1034,26 @@ public:
     return deployEngine;
   }
 
+  void deploy(unsigned flags, BackupMode backupMode, bool bStop, bool bStart)
+  {
+    switch (backupMode)
+    {
+      case DEBACKUP_NONE:
+          check();
+          CEnvironmentDeploymentEngine::deploy(flags, false);
+          break;
+
+      case DEBACKUP_COPY:
+          throw MakeStringException(-1, "Invalid option Backup copy while generating configurations");
+
+      case DEBACKUP_RENAME:
+          throw MakeStringException(-1, "Invalid option Backup rename while generating configurations");
+
+      default:
+          throw MakeStringException(-1, "Invalid option while generating configurations");
+    }
+  }
+
 private:
   StringBuffer m_inDir;
   StringBuffer m_outDir;

+ 1 - 0
deployment/deploy/thorconfiggenengine.cpp

@@ -38,6 +38,7 @@ CThorConfigGenEngine::CThorConfigGenEngine(IEnvDeploymentEngine& envDepEngine,
 //---------------------------------------------------------------------------
 void CThorConfigGenEngine::check()
 {
+   m_instanceCheck = false;
    CDeploymentEngine::check();
 
    const char* dali  = m_process.queryProp("@daliServers");

+ 23 - 48
deployment/deployutils/deployutils.cpp

@@ -3118,6 +3118,18 @@ void addInstanceToCompTree(const IPropertyTree* pEnvRoot,const IPropertyTree* pI
       }
     }
   }
+
+  int nCount = 1;
+  xpath.clear().appendf("Instance");
+  Owned<IPropertyTreeIterator> iter = pCompTree->getElements(xpath.str());
+  StringBuffer sName;
+
+  ForEach(*iter)
+  {
+    sName.clear().append("s").append(nCount);
+    iter->query().setProp(XML_ATTR_NAME, sName.str());
+    nCount++;
+  }
 }
 
 void formIPList(const char* ip, StringArray& formattedIpList)
@@ -3609,7 +3621,7 @@ void getTempPath(char* tempPath, unsigned int bufsize, const char* subdir/*=NULL
 }
 #endif
 
-bool validateEnv(IConstEnvironment* pConstEnv)
+bool validateEnv(IConstEnvironment* pConstEnv, bool abortOnException)
 {
   char tempdir[_MAX_PATH];
   StringBuffer sb;
@@ -3628,7 +3640,7 @@ bool validateEnv(IConstEnvironment* pConstEnv)
 
   try
   {
-    CConfigEngCallback callback(false, true);
+    CConfigEngCallback callback(false, abortOnException);
     Owned<IEnvDeploymentEngine> configGenMgr;
     Owned<IPropertyTree> pEnvRoot = &pConstEnv->getPTree();
     const char* inDir = pEnvRoot->queryProp(XML_TAG_ENVSETTINGS"/path");
@@ -3637,56 +3649,19 @@ bool validateEnv(IConstEnvironment* pConstEnv)
     configGenMgr.setown(createConfigGenMgr(*pConstEnv, callback, NULL, inDir?sb.str():STANDARD_CONFIGXMLDIR, tempdir, NULL, NULL, NULL));
     configGenMgr->deploy(DEFLAGS_CONFIGFILES, DEBACKUP_NONE, false, false);
     deleteRecursive(tempdir);
+    const char* msg = callback.getErrorMsg();
+    if (msg && *msg)
+    {
+      StringBuffer sb("Errors or warnings were found when validating the environment.\n\n");
+      sb.append(msg).append("\n");
+      sb.appendf("Total errors/warnings: %d", callback.getErrorCount() - 1);
+      throw MakeStringExceptionDirect(-1, sb.str());
+    }
   }
   catch(IException* e)
   {
     deleteRecursive(tempdir);
-    StringBuffer sb, newMsg, errMsg;
-    e->errorMessage(sb);
-    String str(sb.trim());
-    String* sub1 = str.substring(str.lastIndexOf('[') + 1, str.length() - 1);
-    if (sub1)
-    {
-      String* sub2 = sub1->substring(sub1->indexOf(':') + 1, sub1->length());
-      if (sub2)
-      {
-        StringBuffer sb2(*sub2);
-        sb2.trim();
-        sb2.replaceString("  ", " ");
-        errMsg.append("Error: ").append(sb2.str()).append("\n");
-        delete sub2;
-      }
-
-      delete sub1;
-    }
-
-    if (errMsg.length())
-    {
-      sb.replaceString(": ", "=");
-      try
-      {
-        Owned<IProperties> pParams = createProperties();
-        pParams->loadProps(sb.str());
-
-        const char* ptype = pParams->queryProp("Process type");
-        if (ptype)
-          newMsg.appendf("Component Type: %s\n", ptype);
-
-        const char* cname = pParams->queryProp("Component");
-        if (cname)
-          newMsg.appendf("Component Name: %s\n", cname);
-      }
-      catch(IException* e1)
-      {
-        e1->Release();
-        throw e;
-      }
-
-      e->Release();
-      throw MakeStringException(-1, "%s", newMsg.append(errMsg.str()).str());
-    }
-    else
-      throw e;
+    throw e;
   }
 
   return true;

+ 1 - 1
deployment/deployutils/deployutils.hpp

@@ -83,5 +83,5 @@ extern DEPLOYUTILS_API void getSummary(const IPropertyTree* pEnvRoot, StringBuff
 extern DEPLOYUTILS_API void mergeAttributes(IPropertyTree* pTo, IPropertyTree* pFrom);
 extern DEPLOYUTILS_API void addEspBindingInformation(const char* xmlArg, IPropertyTree* pEnvRoot, StringBuffer& sbNewName, IConstEnvironment* pConstEnv);
 extern DEPLOYUTILS_API bool updateDirsWithConfSettings(IPropertyTree* pEnvRoot, IProperties* pParams, bool ovrLog = true, bool ovrRun = true);
-extern DEPLOYUTILS_API bool validateEnv(IConstEnvironment* pConstEnv);
+extern DEPLOYUTILS_API bool validateEnv(IConstEnvironment* pConstEnv, bool abortOnException = true);
 #endif

+ 12 - 0
esp/files/configmgr.html

@@ -612,6 +612,18 @@ You need to have Javascript enabled in order to use ConfigMgr. Please enable Jav
 <div class="ft sumpage" style="background-color:White;"></div>   
 </div>   
 
+<div id='validationErrPage' style="display:none;background-color:White;">
+  <div class="hd" style="background-color:White;">ConfigMgr - Validation errors/warnings</div>
+    <div class="bd" style="background-color:White"><p />
+       <div id='validationErrLayout' style="background-color:White;border:none">
+         <div id='validationErrLayoutTextArea' style="background-color:White;border:none">
+            <textarea id="validationErrs" name="validationErrs" rows="30" cols="80" readonly wrap></textarea>
+         </div>
+       </div>
+    </div>
+<div class="ft" style="background-color:White;"></div>
+</div>
+
 <div id="messagePanel" style="display:none;">
   <div class="bd"></div>
 </div>

+ 163 - 8
esp/files/scripts/configmgr/navtree.js

@@ -1579,10 +1579,32 @@ function unlockEnvironment(navtable, saveEnv) {
           var isErr = false;
           if (temp.length > 0) {
             var temp1 = temp[1].split(/<\/XmlArgs>/g);
-            if (temp1.length > 0 && temp1[0].length > 0 && temp1[0].charAt(0) != '<') {
-              isErr = true;
-              updateEnvCtrls(true);
-              alert(temp1[0]);
+            if (temp1.length > 0 && temp1[0].length > 0) {
+              if (temp1[0].indexOf("<Warning>") === 0) {
+                var warning = o.responseText.split(/<Warning>/g);
+                if (warning.length > 0) {
+                  var warning1 = warning[1].split(/<\/Warning>/g);
+                  if (warning1.length > 0 && warning1[0].length > 0 && warning1[0].charAt(0) != '<')
+                  {
+                    alert(warning1[0]);
+                    var err = o.responseText.split(/<\/Warning>/g);
+                    if (err.length > 1) {
+                      var err1 = err[1].split(/<\/XmlArgs>/g);
+                      if (err1.length > 0 && err1[0].length > 0 && err1[0].charAt(0) != '<') {
+                        isErr = true;
+                        updateEnvCtrls(true);
+                        alert(err1[0]);
+                      }
+                    }
+                  }
+                }
+              }
+              else if (temp1[0].charAt(0) != '<')
+              {
+                isErr = true;
+                updateEnvCtrls(true);
+                alert(temp1[0]);
+              }
             }
           }
 
@@ -1735,6 +1757,14 @@ function saveEnvironment(saveas) {
           if (LastSaved1[0].charAt(0) != '<')
             form.lastSaved.value = LastSaved1[0];
 
+          var xmlargs = o.responseText.split(/<XmlArgs>/g);
+          if (xmlargs.length > 0) {
+            var xmlargs1 = xmlargs[1].split(/<\/XmlArgs>/g);
+            if (xmlargs1.length > 0 && xmlargs1[0].length > 0 && xmlargs1[0].charAt(0) != '<') {
+              promptValidationErrs(xmlargs1[0]);
+            }
+          }
+
           refresh();
           form.saveInProgress.value = "false";
         }
@@ -1818,7 +1848,7 @@ function validateEnvironment() {
         if (o.responseText.indexOf("<html") === 0) {
           var temp = o.responseText.split(/td align=\"left\">/g);
           var temp1 = temp[1].split(/<\/td>/g);
-          alert(temp1[0]);
+          promptValidationErrs(temp1[0]);
         }
       }
       else {
@@ -2059,11 +2089,62 @@ function displayAddInstanceDlg() {
             alert("Cannot add instances for the following computers as there can be only one instance of a component per computer.\n" + dup1[0]);
           }
         }
+
+        var reqdcomps = o.responseText.split(/<AddReqdComps>/g);
+        if (reqdcomps.length > 1) {
+          var reqdcomps1 = reqdcomps[1].split(/<\/AddReqdComps>/g);
+          if (reqdcomps1.length > 1 && reqdcomps1[0].length > 0) {
+            var reqNames = o.responseText.split(/<ReqdCompNames>/g);
+            if (reqNames.length > 1) {
+              var reqNames1 = reqNames[1].split(/<\/ReqdCompNames>/g);
+              if (reqNames1.length > 1 && reqNames1[0].length > 0) {
+                var msg = "Following required component(s) '" + reqNames1[0] + "' do not have instance(s) on the following computer(s) '" + reqdcomps1[0] + "'. ";
+                msg += "Would you like to add instances of the required components on these computers?";
+                var fnyes = function() {
+                  this.hide();
+                  var xmlStr1 = "<ReqdComps>";
+                  var ips = reqdcomps1[0].split(/\n/g);
+
+                  for (var i = 0; i < ips.length; i++)
+                    xmlStr1 += "<Computer netAddress=\"" + ips[i] + "\"/>";
+
+                  xmlStr1 += "</ReqdComps>";
+
+                  YAHOO.util.Connect.asyncRequest('POST', '/WsDeploy/AddReqdComps', {
+                    success: function(o) {
+                      var failed = o.responseText.split(/<Failures>/g);
+
+                      if (failed.length > 1) {
+                        var failed1 = failed[1].split(/<\/Failures>/g);
+                        if (failed1.length > 1 && failed1[0].length > 0) {
+                          var msg1 = "Failed to add required components for the following addresses.\n" + reqNames1[0];
+                          msg1 += "\nPlease add instances for the following components manually\n" + reqNames1[0];
+                          alert(msg1);
+                        }
+                      }
+                    },
+                    failure: function(o) {
+                      top.document.stopWait();
+                      alert(o.statusText);
+                    },
+                    scope: this
+                  },
+                  getFileName(true) + 'XmlArgs=' + xmlStr1);
+                }
+
+                var fnno = function() {this.hide();}
+                promptYesNoCancel(msg, fnyes, fnno, null);
+              }
+            }
+          }
+        }
+
         var form = top.window.document.forms['treeForm'];
         form.isChanged.value = "true";
         top.document.stopWait();
         clickCurrentSelOrName(tmpdt);
         var temp = o.responseText.split(/<NewName>/g);
+
         if (temp.length > 1) {
           var temp1 = temp[1].split(/<\/NewName>/g);
           if (temp1.length > 1) {
@@ -3125,15 +3206,75 @@ function promptYesNoCancel(msg, fnYes, fnNo, fnCancel) {
   }
 
   var myButtons = [{ text: "Yes", handler: fnYes, isDefault: true },
-                      { text: "No", handler: fnNo },
-                      { text: "Cancel", handler: fnCancel}];
-                      tmpdt.YNCancelPanel.cfg.queueProperty("buttons", myButtons);
+                      { text: "No", handler: fnNo }];
+
+  if (fnCancel !== null)
+    myButtons[myButtons.length] = { text: "Cancel", handler: fnCancel};
+
+  tmpdt.YNCancelPanel.cfg.queueProperty("buttons", myButtons);
   document.getElementById('YesNoCancelPanel').style.display = 'block';
   tmpdt.YNCancelPanel.setBody(msg);
   tmpdt.YNCancelPanel.render(document.body);
   tmpdt.YNCancelPanel.show();
 }
 
+function promptValidationErrs(msg) {
+ if(!top.document.ValidationErrPanel){
+    var fn = function () { this.hide(); }
+    top.document.ValidationErrPanel = new YAHOO.widget.Dialog('validationErrPage',
+    {
+      width:500,
+      height :550,
+      visible : false,
+      draggable : true,
+      modal : true,
+      close : false,
+      constraintoviewport: true,
+      underlay: 'none',
+      buttons :[ { text:"Ok", handler:fn, isDefault:true}]
+    });
+
+    top.document.ValidationErrPanel.renderEvent.subscribe(function(){
+     if (!top.document.ValidationErrPanel.layout) {
+        top.document.ValidationErrPanel.layout = new YAHOO.widget.Layout('validationErrLayout', {
+        height: (top.document.ValidationErrPanel.body.offsetHeight - 25),
+        units: [
+           { position: 'center' , body: 'validationErrLayoutTextArea'} ]
+        });
+        top.document.ValidationErrPanel.layout.render();
+      }
+    });
+  }
+
+  resize = new YAHOO.util.Resize('validationErrPage', {
+    handles: ['br'],
+    autoRatio: true,
+    status: false,
+    minWidth: 280,
+    minHeight: 350
+  });
+
+  resize.on('resize', function(args) {
+     var panelHeight = args.height,
+     padding = 20;
+     YAHOO.util.Dom.setStyle('validationErrLayout', 'display', 'none');
+     this.cfg.setProperty("height", panelHeight + 'px');
+     top.document.ValidationErrPanel.layout.set('height', this.body.offsetHeight - padding);
+     top.document.ValidationErrPanel.layout.set('width', this.body.offsetWidth - padding);
+     YAHOO.util.Dom.setStyle('validationErrs', 'height', this.body.offsetHeight - padding - 10);
+     YAHOO.util.Dom.setStyle('validationErrs', 'width', this.body.offsetWidth - padding);
+     YAHOO.util.Dom.setStyle('validationErrLayout', 'display', 'block');
+     top.document.ValidationErrPanel.layout.resize();
+
+  }, top.document.ValidationErrPanel, true);
+
+  document.getElementById('validationErrPage').style.display = 'block';
+  document.getElementById('validationErrs').value = msg;
+  top.document.ValidationErrPanel.render();
+  top.document.ValidationErrPanel.show();
+  top.document.ValidationErrPanel.center();
+}
+
 function unlockUser() {
   //onbeforeunload handles unsaved changes. At this point, user
   //doesn't care about unsaved changes
@@ -3557,6 +3698,20 @@ function unlockEnvForWizard(fnCallback, saveEnv){
           if (o.responseText.indexOf("<?xml") === 0) {
             var form = document.forms['treeForm'];
             form.isWizLocked.value = "false";
+            var temp = o.responseText.split(/<XmlArgs>/g);
+            if (temp.length > 0) {
+              var temp1 = temp[1].split(/<\/XmlArgs>/g);
+              if (temp1.length > 0 && temp1[0].length > 0) {
+                if (temp1[0].indexOf("<Warning>") === 0) {
+                  var warning = o.responseText.split(/<Warning>/g);
+                  if (warning.length > 0) {
+                    var warning1 = warning[1].split(/<\/Warning>/g);
+                    if (warning1.length > 0 && warning1[0].length > 0 && warning1[0].charAt(0) != '<')
+                      alert(warning1[0]);
+                  }
+                }
+              }
+            }
           }
           else if (o.responseText.indexOf("<html") === 0) {
             var temp = o.responseText.split(/td align=\"left\">/g);

+ 16 - 0
esp/scm/WsDeploy.ecm

@@ -277,6 +277,20 @@ ESPresponse [exceptions_inline, encode(0)] HandleInstanceResponse
     string Status;
     string NewName;
     string Duplicates;
+    string ReqdCompNames;
+    string AddReqdComps;
+};
+
+ESPrequest AddReqdCompsRequest
+{
+    string XmlArgs;
+    ESPstruct WsDeployReqInfo ReqInfo;
+};
+
+ESPresponse [exceptions_inline, encode(0)] AddReqdCompsResponse
+{
+    string Status;
+    string Failures;
 };
 
 ESPrequest HandleEspServiceBindingsRequest
@@ -578,6 +592,8 @@ ESPservice [exceptions_inline("xslt/exceptions.xslt")] WsDeploy
         GetSubnetIPAddr(GetSubnetIPAddrRequest, GetSubnetIPAddrResponse);
     ESPmethod[description("To get the environment summary"), help("")]
             GetSummary(GetSummaryRequest, GetSummaryResponse);
+    ESPmethod[description("Add the required components on given ip addresses."), help("")]
+        AddReqdComps(AddReqdCompsRequest, AddReqdCompsResponse);
 };
 
 SCMexportdef(WsDeploy);

+ 208 - 23
esp/services/WsDeploy/WsDeployService.cpp

@@ -703,7 +703,7 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
       StringBuffer xml;
       try 
       {
-        StringBuffer sbUser, sbUserIp;
+        StringBuffer sbUser, sbUserIp, errMsg;
         context.getUserID(sbUser);
         sbUser.clear().append(req.getReqInfo().getUserId());
         context.getPeer(sbUserIp);
@@ -713,13 +713,17 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
           Owned<IPropertyTree> pSrcTree = createPTreeFromXMLString(xmlArg && *xmlArg ? xmlArg : "<XmlArgs/>");
           IPropertyTree* pSaveEnv = pSrcTree->queryPropTree("SaveEnv[@flag='true']");
 
+          StringBuffer sbErrMsg;
           if (pSaveEnv)
           {
+            StringBuffer sb;
             m_skipEnvUpdateFromNotification = true;
-            saveEnvironment(&context, &req.getReqInfo());
+            saveEnvironment(&context, &req.getReqInfo(), sb);
+
+            if (sb.length())
+              sbErrMsg.appendf("<Warning>%s</Warning>", sb.str());
           }
 
-          StringBuffer sbErrMsg;
           unlockEnvironment(&context, &req.getReqInfo(), xmlArg, sbErrMsg, pSaveEnv != NULL);
           if (sbErrMsg.length())
           {
@@ -795,7 +799,7 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
     else if (!stricmp(cmd, "SaveEnvironment"))
     {
       StringBuffer xml;
-      StringBuffer sbUser, sbIp;
+      StringBuffer sbUser, sbIp, sbErrMsg;
       sbUser.clear().append(req.getReqInfo().getUserId());
       context.getPeer(sbIp);
 
@@ -803,7 +807,11 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
          !strcmp(m_userWithLock.str(), sbUser.str()) && !strcmp(m_userIp.str(), sbIp.str()) && 
          m_Environment != NULL)
       {
-        saveEnvironment(&context, &req.getReqInfo());
+        saveEnvironment(&context, &req.getReqInfo(), sbErrMsg);
+
+        if (sbErrMsg.length())
+          resp.setXmlArgs(sbErrMsg.str());
+
         StringBuffer tmp;
         m_lastSaved.getString(tmp);
         resp.setLastSaved(tmp.str());
@@ -830,6 +838,8 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
       if (envSaveAs && *envSaveAs)
       {
         StringBuffer filePath(m_pService->getSourceDir());
+        StringBuffer sbErrMsg;
+
         if (filePath.charAt(filePath.length() - 1) != PATHSEPCHAR)
           filePath.append(PATHSEPCHAR);
         filePath.append(envSaveAs);
@@ -839,8 +849,7 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
              !strcmp(m_userWithLock.str(), sbUser.str()) && !strcmp(m_userIp.str(), sbIp.str()) && 
              m_Environment != NULL)
           {
-            saveEnvironment(&context, &req.getReqInfo());
-            StringBuffer sbErrMsg;
+            saveEnvironment(&context, &req.getReqInfo(), sbErrMsg);
             unlockEnvironment(&context, &req.getReqInfo(), "", sbErrMsg);
             if (sbErrMsg.length())
             {
@@ -856,7 +865,11 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
           }
           else if (m_userWithLock.length() == 0 && m_userIp.length() == 0)
           {
-            saveEnvironment(&context, &req.getReqInfo(), true);
+            saveEnvironment(&context, &req.getReqInfo(), sbErrMsg, true);
+
+            if (sbErrMsg.length())
+              resp.setXmlArgs(sbErrMsg.str());
+
             StringBuffer tmp;
             m_lastSaved.getString(tmp);
             resp.setLastSaved(tmp.str());
@@ -867,7 +880,7 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
         else
         {
           CWsDeployFileInfo* fi = m_pService->getFileInfo(envSaveAs, true);
-          StringBuffer sbUser, sbIp, sbXml;
+          StringBuffer sbUser, sbIp, sbXml, sbErrMsg;
           if ((fi->isLocked(sbUser, sbIp) && m_userWithLock.length() != 0 && m_userIp.length() != 0 &&
                !strcmp(m_userWithLock.str(), sbUser.str()) && !strcmp(m_userIp.str(), sbIp.str())) ||
                (!fi->isLocked(sbUser, sbIp) && m_userWithLock.length() != 0 && m_userIp.length() != 0))
@@ -876,13 +889,12 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
             toXML(&m_constEnvRdOnly->getPTree(), sbXml);
         
           if (fi->updateEnvironment(sbXml))
-            fi->saveEnvironment(NULL, &req.getReqInfo(), true);
+            fi->saveEnvironment(NULL, &req.getReqInfo(), sbErrMsg, true);
           else
             throw MakeStringException(-1, "Environment Save as operation has failed");
 
           if (m_userWithLock.length() != 0 && m_userIp.length() != 0)
           {
-            StringBuffer sbErrMsg;
             unlockEnvironment(&context, &req.getReqInfo(), "", sbErrMsg);
             if (sbErrMsg.length())
             {
@@ -891,6 +903,9 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
             }
           }
 
+          if (sbErrMsg.length())
+            resp.setXmlArgs(sbErrMsg.str());
+
           StringBuffer tmp;
           fi->getLastSaved(tmp);
           resp.setLastSaved(tmp.str());
@@ -912,7 +927,7 @@ bool CWsDeployFileInfo::navMenuEvent(IEspContext &context,
          m_Environment != NULL)
       {
         if (m_envFile.length())
-          validateEnv((IConstEnvironment*)m_Environment);
+          validateEnv((IConstEnvironment*)m_Environment, false);
         
         resp.setComponent( "WsDeploy" );
         resp.setCommand ( "ValidateEnvironment" );
@@ -4029,7 +4044,7 @@ bool CWsDeployFileInfo::handleInstance(IEspContext &context, IEspHandleInstanceR
   buildSetIter->first();
   IPropertyTree* pBuildSet = &buildSetIter->query();
   const char* processName = pBuildSet->queryProp(XML_ATTR_PROCESS_NAME);
-  StringBuffer dups;
+  StringBuffer dups, reqdComps, reqdCompNames;
 
   if (!strcmp(operation, "Add"))
   {
@@ -4111,8 +4126,13 @@ bool CWsDeployFileInfo::handleInstance(IEspContext &context, IEspHandleInstanceR
           }
         }
       }
+
+      if (!checkForRequiredComponents(pEnvRoot, pComputerNode->queryProp(XML_ATTR_NETADDRESS), reqdCompNames))
+        reqdComps.appendf("\n%s", pComputerNode->queryProp(XML_ATTR_NETADDRESS));
     }
 
+    resp.setReqdCompNames(reqdCompNames.str());
+    resp.setAddReqdComps(reqdComps.str());
     resp.setDuplicates(dups.str());
   }
   else if (!strcmp(operation, "Delete"))
@@ -4164,6 +4184,31 @@ bool CWsDeployFileInfo::handleInstance(IEspContext &context, IEspHandleInstanceR
   return true;
 }
 
+bool CWsDeployFileInfo::addReqdComps(IEspContext &context, IEspAddReqdCompsRequest &req, IEspAddReqdCompsResponse &resp)
+{
+  synchronized block(m_mutex);
+  checkForRefresh(context, &req.getReqInfo(), true);
+
+  const char* xmlArg = req.getXmlArgs();
+  Owned<IPropertyTree> instances = createPTreeFromXMLString(xmlArg && *xmlArg ? xmlArg : "<ReqdComps/>");
+  Owned<IPropertyTree> pEnvRoot = getEnvTree(context, &req.getReqInfo());
+
+  Owned<IPropertyTreeIterator> iterComp = instances->getElements("*");
+  bool bAdded = false;
+  StringBuffer reqCompNames, failed;
+
+  ForEach(*iterComp)
+  {
+    IPropertyTree& pComputer = iterComp->query();
+
+    if (!checkForRequiredComponents(pEnvRoot, pComputer.queryProp(XML_ATTR_NETADDRESS), reqCompNames, true))
+      failed.appendf("\n%s", pComputer.queryProp(XML_ATTR_NETADDRESS));
+  }
+
+  resp.setFailures(failed.str());
+  resp.setStatus("true");
+  return true;
+}
 
 bool CWsDeployFileInfo::handleEspServiceBindings(IEspContext &context, IEspHandleEspServiceBindingsRequest &req, IEspHandleEspServiceBindingsResponse &resp)
 {
@@ -5189,21 +5234,30 @@ void CWsDeployFileInfo::getNavigationData(IEspContext &context, IPropertyTree* p
   }
 }
 
-void CWsDeployFileInfo::saveEnvironment(IEspContext* pContext, IConstWsDeployReqInfo *reqInfo, bool saveAs)
+void CWsDeployFileInfo::saveEnvironment(IEspContext* pContext, IConstWsDeployReqInfo *reqInfo, StringBuffer& errMsg, bool saveAs)
 {
   if (m_envFile.length())
   {
     Owned<IPropertyTree> pEnvRoot;
-    
-    if (!saveAs || (saveAs && m_Environment))
+    StringBuffer valerrs;
+
+    try
     {
-      pEnvRoot.setown(&m_Environment->getPTree());
-      validateEnv((IConstEnvironment*)m_Environment);
+      if (!saveAs || (saveAs && m_Environment))
+      {
+        pEnvRoot.setown(&m_Environment->getPTree());
+        validateEnv((IConstEnvironment*)m_Environment, false);
+      }
+      else if (saveAs)
+      {
+        pEnvRoot.setown(&m_constEnvRdOnly->getPTree());
+        validateEnv(m_constEnvRdOnly, false);
+      }
     }
-    else if (saveAs)
+    catch(IException* e)
     {
-      pEnvRoot.setown(&m_constEnvRdOnly->getPTree());
-      validateEnv(m_constEnvRdOnly);
+      e->errorMessage(valerrs);
+      e->Release();
     }
 
     //save and write to backup
@@ -5298,6 +5352,9 @@ void CWsDeployFileInfo::saveEnvironment(IEspContext* pContext, IConstWsDeployReq
     //reset the readonly tree
     Owned<IEnvironmentFactory> factory = getEnvironmentFactory();
     m_constEnvRdOnly.setown(factory->loadLocalEnvironment(sXML.str()));
+
+    if (valerrs.length())
+      errMsg.appendf("Save operation was successful. However the following exceptions were raised.\n%s", valerrs.str());
   }
   else
   {
@@ -5405,7 +5462,7 @@ void CWsDeployFileInfo::unlockEnvironment(IEspContext* context, IConstWsDeployRe
     if (!ret || sbMsg.length())
     {
       if (saveEnv)
-        sbErrMsg.clear().append("Save operation is successful. However, ");
+        sbErrMsg.append("Save operation is successful. However, ");
       sbErrMsg.appendf("Write access to the Environment cannot be revoked. Reason(s):\n%s", sbMsg.str());
       return;
     }
@@ -5846,8 +5903,9 @@ void CWsDeployFileInfo::initFileInfo(bool createOrOverwrite)
 
   try
   {
+    StringBuffer err;
     if (modified && fileExists)
-      saveEnvironment(NULL, NULL);
+      saveEnvironment(NULL, NULL, err);
   }
   catch(IException* e)
   {
@@ -6314,6 +6372,13 @@ bool CWsDeployExCE::onGetSummary(IEspContext &context, IEspGetSummaryRequest &re
   CWsDeployFileInfo* fi = getFileInfo(req.getReqInfo().getFileName(), true);
   return fi->getSummary(context, req, resp);
 }
+
+bool CWsDeployExCE::onAddReqdComps(IEspContext &context, IEspAddReqdCompsRequest &req, IEspAddReqdCompsResponse &resp)
+{
+  CWsDeployFileInfo* fi = getFileInfo(req.getReqInfo().getFileName(), true);
+  return fi->addReqdComps(context, req, resp);
+}
+
 bool CWsDeployEx::onDeploy(IEspContext &context, IEspDeployRequest& req, IEspDeployResponse& resp)
 {
   CWsDeployFileInfo* fi = getFileInfo(req.getReqInfo().getFileName());
@@ -6637,3 +6702,123 @@ CWsDeployExCE* createWsDeployCE(IPropertyTree *cfg, const char* name)
   
   return new CWsDeployExCE;
 }
+
+bool CWsDeployFileInfo::checkForRequiredComponents(IPropertyTree* pEnvRoot, const char* ip,
+                                                   StringBuffer& reqdCompNames, bool autoadd/*=false*/)
+{
+  StringBuffer prop, xpath, genEnvConf;
+  Owned<IProperties> algProps;
+  StringArray compOnAllNodes;
+  bool retVal = true;
+
+  xpath.clear().appendf("Software/EspProcess/EspService[@name='%s']/LocalConfFile", m_pService->getName());
+  const char* pConfFile = m_pService->getCfg()->queryProp(xpath.str());
+  xpath.clear().appendf("Software/EspProcess/EspService[@name='%s']/LocalEnvConfFile", m_pService->getName());
+  const char* pEnvConfFile = m_pService->getCfg()->queryProp(xpath.str());
+
+  if (pConfFile && *pConfFile && pEnvConfFile && *pEnvConfFile)
+  {
+    Owned<IProperties> pParams = createProperties(pConfFile);
+    Owned<IProperties> pEnvParams = createProperties(pEnvConfFile);
+    const char* genenv = pParams->queryProp("wizardalgorithm");
+
+    if (genenv && *genenv)
+    {
+      const char* cfgpath = pEnvParams->queryProp("configs");
+
+      if (!cfgpath || !*cfgpath)
+        cfgpath = CONFIG_DIR;
+
+      genEnvConf.clear().append(cfgpath);
+
+      if (genEnvConf.charAt(genEnvConf.length() - 1) != PATHSEPCHAR)
+        genEnvConf.append(PATHSEPCHAR);
+
+      genEnvConf.append(genenv);
+
+      if(checkFileExists(genEnvConf.str()))
+        algProps.setown(createProperties(genEnvConf.str()));
+      else
+        throw MakeStringException( -1 , "The algorithm file %s does not exists", genEnvConf.str());
+
+      algProps->getProp("comps_on_all_nodes", prop);
+      DelimToStringArray(prop.str(), compOnAllNodes, ",");
+      const char* flag = pParams->queryProp("autoaddallnodescomp");
+
+      if (!autoadd)
+        autoadd = (flag && *flag == '1') ? true : false;
+    }
+  }
+
+  for(unsigned i = 0; i < compOnAllNodes.ordinality(); i++)
+  {
+    xpath.clear().appendf("./Programs/Build/BuildSet[@name=\"%s\"]", compOnAllNodes.item(i));
+    Owned<IPropertyTreeIterator> buildSetIter = pEnvRoot->getElements(xpath.str());
+    buildSetIter->first();
+    IPropertyTree* pBuildSet;
+
+    if (buildSetIter->isValid())
+      pBuildSet = &buildSetIter->query();
+    else
+      continue;
+
+    const char* processName = pBuildSet->queryProp(XML_ATTR_PROCESS_NAME);
+    xpath.clear().appendf("./Software/%s[1]", processName);
+    IPropertyTree* pCompTree = pEnvRoot->queryPropTree(xpath.str());
+
+    if (!pCompTree)
+    {
+      if (autoadd)
+      {
+        StringBuffer buildSetPath, sbNewName;
+        Owned<IPropertyTree> pSchema = loadSchema(pEnvRoot->queryPropTree("./Programs/Build[1]"), pBuildSet, buildSetPath, m_Environment);
+        xpath.clear().appendf("./Software/%s[@name='%s']", processName, compOnAllNodes.item(i));
+        pCompTree = generateTreeFromXsd(pEnvRoot, pSchema, processName, false);
+        IPropertyTree* pInstTree = pCompTree->queryPropTree(XML_TAG_INSTANCE);
+
+        if (pInstTree)
+          pCompTree->removeTree(pInstTree);
+
+        addComponentToEnv(pEnvRoot, compOnAllNodes.item(i), sbNewName, pCompTree);
+      }
+      else
+      {
+        reqdCompNames.appendf("%s\n", compOnAllNodes.item(i));
+        retVal = false;
+        continue;
+      }
+    }
+
+    xpath.clear().appendf(XML_TAG_INSTANCE"["XML_ATTR_NETADDRESS"='%s']", ip);
+    IPropertyTree* pInst = pCompTree->queryPropTree(xpath.str());
+
+    if (!pInst)
+    {
+      if (autoadd)
+      {
+        StringBuffer sb, sbl, compName, nodeName;
+        xpath.clear().appendf("./%s/%s[%s=\"%s\"]", XML_TAG_HARDWARE, XML_TAG_COMPUTER,
+          XML_ATTR_NETADDRESS, ip);
+        IPropertyTree* computer = pEnvRoot->queryPropTree(xpath.str());
+
+        if(computer)
+        {
+          nodeName.clear().append(computer->queryProp(XML_ATTR_NAME));
+          xpath.clear().appendf("./%s/%s[%s=\"%s\"]", XML_TAG_SOFTWARE, processName, XML_ATTR_BUILDSET, compOnAllNodes.item(i));
+          sb.clear().appendf("<Instance buildSet=\"%s\" compName=\"%s\" ><Instance name=\"%s\" /></Instance>",
+            compOnAllNodes.item(i), pCompTree->queryProp(XML_ATTR_NAME), nodeName.str());
+          Owned<IPropertyTree> pInstance = createPTreeFromXMLString(sb.str());
+          addInstanceToCompTree(pEnvRoot, pInstance, sbl.clear(), sb.clear(), NULL);
+        }
+      }
+      else
+      {
+        reqdCompNames.appendf("%s\n", compOnAllNodes.item(i));
+        retVal = false;
+        continue;
+      }
+    }
+  }
+
+  return retVal;
+}

+ 4 - 1
esp/services/WsDeploy/WsDeployService.hpp

@@ -288,6 +288,7 @@ public:
     virtual bool rollbackEnvironmentForCloud(IEspContext &context, IEspRollbackEnvironmentForCloudRequest &req, IEspRollbackEnvironmentForCloudResponse &resp);
     virtual bool notifyInitSystemSaveEnvForCloud(IEspContext &context, IEspNotifyInitSystemSaveEnvForCloudRequest &req, IEspNotifyInitSystemSaveEnvForCloudResponse &resp);
     virtual bool getSummary(IEspContext &context, IEspGetSummaryRequest &req, IEspGetSummaryResponse &resp);
+    virtual bool addReqdComps(IEspContext &context, IEspAddReqdCompsRequest &req, IEspAddReqdCompsResponse &resp);
     
     void environmentUpdated()
     {
@@ -325,9 +326,10 @@ private:
     void checkForRefresh(IEspContext &context, IConstWsDeployReqInfo *reqInfo, bool checkWriteAccess);
     IPropertyTree* getEnvTree(IEspContext &context, IConstWsDeployReqInfo *reqInfo);
     void activeUserNotResponding();
-    void saveEnvironment(IEspContext* pContext, IConstWsDeployReqInfo *reqInfo, bool saveAs = false);
+    void saveEnvironment(IEspContext* pContext, IConstWsDeployReqInfo *reqInfo, StringBuffer& errMsg, bool saveAs = false);
     void unlockEnvironment(IEspContext* pContext, IConstWsDeployReqInfo *reqInfo, const char* xmlarg, StringBuffer& sbMsg, bool saveEnv = false);
     void setEnvironment(IEspContext &context, IConstWsDeployReqInfo *reqInfo, const char* newEnv, const char* fnName, StringBuffer& sbBackup, bool validate = true, bool updateDali = true);
+    bool checkForRequiredComponents(IPropertyTree* pEnvRoot, const char* ip, StringBuffer& reqdCompNames, bool autoAdd=false);
   
     Owned<CSdsSubscription>   m_pSubscription;
     Owned<IConstEnvironment>  m_constEnvRdOnly;
@@ -720,6 +722,7 @@ public:
     virtual bool onRollbackEnvironmentForCloud(IEspContext &context, IEspRollbackEnvironmentForCloudRequest &req, IEspRollbackEnvironmentForCloudResponse &resp);
     virtual bool onNotifyInitSystemSaveEnvForCloud(IEspContext &context, IEspNotifyInitSystemSaveEnvForCloudRequest &req, IEspNotifyInitSystemSaveEnvForCloudResponse &resp);
     virtual bool onGetSummary(IEspContext &context, IEspGetSummaryRequest &req, IEspGetSummaryResponse &resp);
+    virtual bool onAddReqdComps(IEspContext &context, IEspAddReqdCompsRequest &req, IEspAddReqdCompsResponse &resp);
 
     void getNavigationData(IEspContext &context, IPropertyTree* pData);
     CWsDeployFileInfo* getFileInfo(const char* fileName, bool addIfNotFound=false, bool createFile = false);

+ 1 - 0
initfiles/componentfiles/configxml/CMakeLists.txt

@@ -83,6 +83,7 @@ FOREACH( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/roxievars_linux.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/sasha.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/eclcc.xsl
+    ${CMAKE_CURRENT_SOURCE_DIR}/validateAll.xsl
 )
     Install ( FILES ${iFILES} DESTINATION ${OSSDIR}/componentfiles/configxml COMPONENT Runtime)
 ENDFOREACH ( iFILES )

+ 97 - 0
initfiles/componentfiles/configxml/validateAll.xsl

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+################################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+################################################################################
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:space="default"
+xmlns:seisint="http://seisint.com" exclude-result-prefixes="seisint">
+  <xsl:output method="xml" indent="yes"/>
+
+  <xsl:variable name="apos">'</xsl:variable>
+
+  <xsl:template match="/Environment">
+    <xsl:apply-templates select="Hardware/Computer"/>
+    <xsl:apply-templates select="Software/ThorCluster"/>
+  </xsl:template>
+
+  <xsl:template match="Computer">
+    <xsl:variable name="instanceIp" select="@netAddress"/>
+    <xsl:variable name="hasInstance" select="/Environment/DeployComponents/*/Instance[@netAddress=$instanceIp]"/>
+    <xsl:choose>
+      <xsl:when test="$hasInstance">
+        <xsl:variable name="dafileSrvNode" select="/Environment/DeployComponents/*[name()='DafilesrvProcess']/Instance[@netAddress=$instanceIp]"/>
+        <xsl:choose>
+          <xsl:when test="not($dafileSrvNode)">
+            <xsl:call-template name="validationMessage">
+              <xsl:with-param name="msg" select="concat('The computer ', $apos, $instanceIp, $apos, ' does not have a DafileSrvProcess Instance!')"/>
+            </xsl:call-template>
+          </xsl:when>
+        </xsl:choose>
+      </xsl:when>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template match="ThorCluster">
+    <xsl:variable name="thorMasterNode" select="./ThorMasterProcess[@computer]"/>
+    <xsl:choose>
+      <xsl:when test="$thorMasterNode">
+        <xsl:if test="string(@localThor) = 'true' and count(./ThorSlaveProcess[@computer!=$thorMasterNode/@computer]) > 0">
+          <xsl:call-template name="validationMessage">
+            <xsl:with-param name="msg" select="'Thor attribute localThor cannot be true when the master and slave processes are on different nodes!'"/>
+          </xsl:call-template>
+        </xsl:if>
+      </xsl:when>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template name="validationMessage">
+    <xsl:param name="type" select="'error'"/>
+    <xsl:param name="compType"/>
+    <xsl:param name="compName"/>
+    <xsl:param name="msg"/>
+    <!--ask deployment tool to display this validation error -->
+    <!--format is like: error:EspProcess:esp1:This is a message.-->
+    <xsl:variable name="encodedMsg" select="concat($type, ':', $compType, ':', $compName, ':', $msg)"/>
+    <xsl:choose>
+      <xsl:when test="function-available('seisint:validationMessage')">
+        <xsl:variable name="dummy" select="seisint:validationMessage($encodedMsg)"/>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:call-template name="message">
+          <xsl:with-param name="text" select="concat('Validation for ', $compType, ' named ', $compName, ': ', $msg)"/>
+        </xsl:call-template>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template name="message">
+    <xsl:param name="text"/>
+    <xsl:choose>
+      <xsl:when test="function-available('seisint:message')">
+        <xsl:variable name="dummy" select="seisint:message($text)"/>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:message terminate="no">
+          <xsl:value-of select="$text"/>
+        </xsl:message>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+</xsl:stylesheet>

+ 0 - 11
initfiles/etc/DIR_NAME/environment.xml.in

@@ -959,17 +959,6 @@
                          service="ecldirect"/>
    </Properties>
   </EspService>
-  <FTSlaveProcess build="${CPACK_RPM_PACKAGE_VERSION}_${CPACK_RPM_PACKAGE_RELEASE}"
-                  buildSet="ftslave"
-                  description="FTSlave process"
-                  name="myftslave"
-                  version="1">
-   <Instance computer="localhost"
-             directory="${RUNTIME_PATH}/myftslave"
-             name="s1"
-             netAddress="."
-             program="${EXEC_PATH}/ftslave"/>
-  </FTSlaveProcess>
   <PluginProcess build="${CPACK_RPM_PACKAGE_VERSION}_${CPACK_RPM_PACKAGE_RELEASE}"
                  buildSet="plugins_auditlib"
                  description="plugin process"

+ 2 - 2
initfiles/etc/DIR_NAME/genenvrules.conf

@@ -1,9 +1,9 @@
 
 [Algorithm]
 max_comps_per_node=4
-do_not_generate=SiteCertificate,dfuplus,soapplus,eclplus,ldapServer,ws_account,eclserver
+do_not_generate=SiteCertificate,dfuplus,soapplus,eclplus,ldapServer,ws_account,eclserver,ftslave
 avoid_combo=dali-eclagent
-comps_on_all_nodes=dafilesrv,ftslave
+comps_on_all_nodes=dafilesrv
 topology_for_comps=thor,hthor,roxie
 do_not_gen_optional=dali,thor
 roxie_agent_redundancy=1,2,1