Преглед на файлове

HPCC-25173 Fix 2 target related calls in cloud environment

1. Common out the code for validating target name.
   Both refreshValidClusters() and isValidCluster() are in ws_sql
service and ws_workunits service. The related code for those 2
methods are moved into the SMCLib. The refreshValidClusters() is
renamed to refreshValidTargets(). The isValidCluster() is renamed
as validateTargetName(). Both of them are upgraded to support both
bare metal environment and cloud environment. The validateTargetName()
is upgraded to thorw an exception if the target name is invalid.
The code in ws_sql service and ws_workunits service is modified
to match with those changes.
2. Add getThorClusterNames() which returns both thor target names
and thor queue names for both bare metal environment and cloud
environment. Replace the existing getEnvironmentThorClusterNames()
call in ws_smcService.cpp.
3. Simplify the code in the CWsSMCEx::onBrowseResources().

Revise based on review:
1. Change the MakeStringException() to makeStringExceptionV().
2. Change the refreshValidClusters() to static. Add targetsDirty
flag. Call the refreshValidClusters() if targetsDirty is true.
In cloud environment, only call refreshValidClusters() once.
3. Change the notEmpty() calls to isEmptyString().
4. Change the BoolHash validTargets to std::set<std::string> validTargets.

Signed-off-by: wangkx <kevin.wang@lexisnexis.com>
wangkx преди 4 години
родител
ревизия
761e72201c

+ 12 - 25
esp/services/ws_smc/ws_smcService.cpp

@@ -1777,11 +1777,11 @@ bool CWsSMCEx::onGetThorQueueAvailability(IEspContext &context, IEspGetThorQueue
     {
         context.ensureFeatureAccess(FEATURE_URL, SecAccess_Read, ECLWATCH_THOR_QUEUE_ACCESS_DENIED, QUEUE_ACCESS_DENIED);
 
-        StringArray thorNames, groupNames, targetNames, queueNames;
-        getEnvironmentThorClusterNames(thorNames, groupNames, targetNames, queueNames);
+        StringArray targetNames, queueNames;
+        getThorClusterNames(targetNames, queueNames);
 
         IArrayOf<IEspThorCluster> ThorClusters;
-        ForEachItemIn(x, thorNames)
+        ForEachItemIn(x, targetNames)
         {
             const char* targetName = targetNames.item(x);
             const char* queueName = queueNames.item(x);
@@ -1913,23 +1913,17 @@ bool CWsSMCEx::onBrowseResources(IEspContext &context, IEspBrowseResourcesReques
 
         double version = context.getClientVersion();
 
-        Owned<IEnvironmentFactory> factory = getEnvironmentFactory(true);
-        Owned<IConstEnvironment> constEnv = factory->openEnvironment();
-
         //The resource files will be downloaded from the same box of ESP (not dali)
         StringBuffer ipStr;
         IpAddress ipaddr = queryHostIP();
         ipaddr.getIpText(ipStr);
         if (ipStr.length() > 0)
-        {
             resp.setNetAddress(ipStr.str());
-            Owned<IConstMachineInfo> machine = constEnv->getMachineByAddress(ipStr.str());
-            if (machine)
-            {
-                int os = machine->getOS();
-                resp.setOS(os);
-            }
-        }
+#ifdef _WIN32
+        resp.setOS(MachineOsW2K);
+#else
+        resp.setOS(MachineOsLinux);
+#endif
 
         if (m_PortalURL.length() > 0)
             resp.setPortalURL(m_PortalURL.str());
@@ -1944,18 +1938,11 @@ bool CWsSMCEx::onBrowseResources(IEspContext &context, IEspBrowseResourcesReques
         //Now, get a list of resources stored inside the ESP box
         IArrayOf<IEspHPCCResourceRepository> resourceRepositories;
 
-        Owned<IPropertyTree> pEnvRoot = &constEnv->getPTree();
-        const char* ossInstall = pEnvRoot->queryProp("EnvSettings/path");
-        if (!ossInstall || !*ossInstall)
-        {
-            OWARNLOG("Failed to get EnvSettings/Path in environment settings.");
-            return true;
-        }
-
-        StringBuffer path;
-        path.appendf("%s/componentfiles/files/downloads", ossInstall);
+        StringBuffer path(getCFD());
+        const char sepChar = getPathSepChar(path);
+        addPathSepChar(path, sepChar).append("files").append(sepChar).append("downloads");
         Owned<IFile> f = createIFile(path.str());
-        if(!f->exists() || !f->isDirectory())
+        if (!f->exists() || (f->isDirectory() != fileBool::foundYes))
         {
             OWARNLOG("Invalid resource folder");
             return true;

+ 12 - 58
esp/services/ws_sql/ws_sqlService.cpp

@@ -30,8 +30,6 @@ void CwssqlEx::init(IPropertyTree *_cfg, const char *_process, const char *_serv
         throw MakeStringException(-1, "ws_sqlEx: Problem initiating ECLFunctions structure");
     }
 
-    refreshValidClusters();
-
     setWsSqlBuildVersion(BUILD_TAG);
 }
 
@@ -831,8 +829,7 @@ void CwssqlEx::processMultipleClusterOption(StringArray & clusters, const char
         hashoptions.appendf("\n#OPTION('AllowedClusters', '%s", targetcluster);
         ForEachItemIn(i,clusters)
         {
-            if (!isValidCluster(clusters.item(i)))
-                throw MakeStringException(-1, "Invalid alternate cluster name: %s", clusters.item(i));
+            validateTargetName(clusters.item(i));
 
             hashoptions.appendf(",%s", clusters.item(i));
         }
@@ -946,11 +943,7 @@ bool CwssqlEx::onExecuteSQL(IEspContext &context, IEspExecuteSQLRequest &req, IE
         if (compiledwuid.length()==0)
         {
             {
-                if (isEmpty(cluster))
-                    throw MakeStringException(-1,"Target cluster not set.");
-
-                if (!isValidCluster(cluster))
-                    throw MakeStringException(-1, "Invalid cluster name: %s", cluster);
+                validateTargetName(cluster);
 
                 if (querytype == SQLTypeCreateAndLoad)
                     clonable = false;
@@ -1018,8 +1011,8 @@ bool CwssqlEx::onExecuteSQL(IEspContext &context, IEspExecuteSQLRequest &req, IE
                 createWUXMLParams(xmlparams, parsedSQL, NULL, cw);
             else if (querytype == SQLTypeSelect)
             {
-                if (notEmpty(cluster) && !isValidCluster(cluster))
-                    throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", cluster);
+                if (!isEmptyString(cluster))
+                    validateTargetName(cluster);
 
                 createWUXMLParams(xmlparams, parsedSQL->getParamList());
             }
@@ -1232,8 +1225,8 @@ bool CwssqlEx::onExecutePreparedSQL(IEspContext &context, IEspExecutePreparedSQL
        context.ensureFeatureAccess(WSSQLACCESS, SecAccess_Write, -1, "WsSQL::ExecutePreparedSQL: Permission denied.");
 
        const char *cluster = req.getTargetCluster();
-       if (notEmpty(cluster) && !isValidCluster(cluster))
-           throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", cluster);
+       if (!isEmptyString(cluster))
+           validateTargetName(cluster);
 
        Owned<IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
 
@@ -1450,11 +1443,7 @@ bool CwssqlEx::onPrepareSQL(IEspContext &context, IEspPrepareSQLRequest &req, IE
             }
             else
             {
-                if (isEmpty(cluster))
-                    throw MakeStringException(1,"Target cluster not set.");
-
-                if (!isValidCluster(cluster))
-                    throw MakeStringException(-1/*ECLWATCH_INVALID_CLUSTER_NAME*/, "Invalid cluster name: %s", cluster);
+                validateTargetName(cluster);
 
                 ECLEngine::generateECL(parsedSQL, ecltext);
                 if (hashoptions.length() > 0)
@@ -1686,8 +1675,8 @@ bool CwssqlEx::onCreateTableAndLoad(IEspContext &context, IEspCreateTableAndLoad
         throw MakeStringException(-1, "WsSQL::CreateTableAndLoad: Error: Target TableName is invalid: %s.", targetTableName);
 
     const char * cluster = req.getTargetCluster();
-    if (notEmpty(cluster) && !isValidCluster(cluster))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "WsSQL::CreateTableAndLoad: Invalid cluster name: %s", cluster);
+    if (!isEmptyString(cluster))
+        validateTargetName(cluster);
 
     IConstDataSourceInfo & datasource = req.getDataSource();
 
@@ -1979,38 +1968,6 @@ bool CwssqlEx::onGetResults(IEspContext &context, IEspGetResultsRequest &req, IE
     return success;
 }
 
-void CwssqlEx::refreshValidClusters()
-{
-    validClusters.kill();
-#ifdef _CONTAINERIZED
-    Owned<IStringIterator> it = getContainerTargetClusters(nullptr, nullptr);
-#else
-    Owned<IStringIterator> it = getTargetClusters(nullptr, nullptr);
-#endif
-    ForEach(*it)
-    {
-        SCMStringBuffer s;
-        IStringVal &val = it->str(s);
-        if (!validClusters.getValue(val.str()))
-            validClusters.setValue(val.str(), true);
-    }
-}
-
-bool CwssqlEx::isValidCluster(const char *cluster)
-{
-    if (!cluster || !*cluster)
-        return false;
-    CriticalBlock block(crit);
-    if (validClusters.getValue(cluster))
-        return true;
-    if (validateTargetClusterName(cluster))
-    {
-        refreshValidClusters();
-        return true;
-    }
-    return false;
-}
-
 bool CwssqlEx::publishWorkunit(IEspContext &context, const char * queryname, const char * wuid, const char * targetcluster)
 {
     Owned<IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
@@ -2019,7 +1976,7 @@ bool CwssqlEx::publishWorkunit(IEspContext &context, const char * queryname, con
         throw MakeStringException(ECLWATCH_CANNOT_OPEN_WORKUNIT,"Cannot find the workunit %s", wuid);
 
     SCMStringBuffer queryName;
-    if (notEmpty(queryname))
+    if (!isEmptyString(queryname))
         queryName.set(queryname);
     else
         queryName.set(cw->queryJobName());
@@ -2028,15 +1985,12 @@ bool CwssqlEx::publishWorkunit(IEspContext &context, const char * queryname, con
         throw MakeStringException(ECLWATCH_MISSING_PARAMS, "Query/Job name not defined for publishing workunit %s", wuid);
 
     SCMStringBuffer target;
-    if (notEmpty(targetcluster))
+    if (!isEmptyString(targetcluster))
         target.set(targetcluster);
     else
         target.set(cw->queryClusterName());
 
-    if (!target.length())
-        throw MakeStringException(ECLWATCH_MISSING_PARAMS, "Cluster name not defined for publishing workunit %s", wuid);
-    if (!isValidCluster(target.str()))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", target.str());
+    validateTargetName(target.str());
     //RODRIGO this is needed:
     //copyQueryFilesToCluster(context, cw, "", target.str(), queryName.str(), false);
 

+ 0 - 5
esp/services/ws_sql/ws_sqlService.hpp

@@ -62,9 +62,6 @@ public:
 class CwssqlEx : public Cwssql
 {
 private:
-    BoolHash validClusters;
-    CriticalSection crit;
-
     IPropertyTree *cfg;
     std::map<std::string,std::string> cachedSQLQueries;
 
@@ -115,8 +112,6 @@ public:
     bool onSetRelatedIndexes(IEspContext &context, IEspSetRelatedIndexesRequest &req, IEspSetRelatedIndexesResponse &resp);
     bool onCreateTableAndLoad(IEspContext &context, IEspCreateTableAndLoadRequest &req, IEspCreateTableAndLoadResponse &resp);
 
-    void refreshValidClusters();
-    bool isValidCluster(const char *cluster);
     void processMultipleClusterOption(StringArray & clusters, const char  * targetcluster, StringBuffer & hashoptions);
 
     void fetchRequiredHpccFiles(IArrayOf<SQLTable> * sqltables);

+ 9 - 22
esp/services/ws_workunits/ws_workunitsQuerySets.cpp

@@ -474,8 +474,7 @@ bool CWsWorkunitsEx::onWUCopyLogicalFiles(IEspContext &context, IEspWUCopyLogica
         cluster.set(req.getCluster());
     else
         cluster.set(cw->queryClusterName());
-    if (!isValidCluster(cluster))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", cluster.str());
+    validateTargetName(cluster);
 
     Owned <IConstWUClusterInfo> clusterInfo = getTargetClusterInfo(cluster.str());
 
@@ -880,10 +879,7 @@ bool CWsWorkunitsEx::onWUPublishWorkunit(IEspContext &context, IEspWUPublishWork
         target.set(req.getCluster());
     else
         target.set(cw->queryClusterName());
-    if (!target.length())
-        throw MakeStringException(ECLWATCH_MISSING_PARAMS, "Cluster name not defined for publishing workunit %s", wuid.str());
-    if (!isValidCluster(target.str()))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", target.str());
+    validateTargetName(target);
 
     DBGLOG("%s publishing wuid %s to target %s as query %s", context.queryUserId(), wuid.str(), target.str(), queryName.str());
 
@@ -1827,11 +1823,9 @@ bool CWsWorkunitsEx::onWUListQueriesUsingFile(IEspContext &context, IEspWUListQu
 bool CWsWorkunitsEx::onWUQueryFiles(IEspContext &context, IEspWUQueryFilesRequest &req, IEspWUQueryFilesResponse &resp)
 {
     const char *target = req.getTarget();
+    validateTargetName(target);
+
     const char *query = req.getQueryId();
-    if (!target || !*target)
-        throw MakeStringException(ECLWATCH_QUERYSET_NOT_FOUND, "Target not specified");
-    if (!isValidCluster(target))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid target name: %s", target);
     if (!query || !*query)
         throw MakeStringException(ECLWATCH_QUERYID_NOT_FOUND, "Query not specified");
     Owned<IPropertyTree> registeredQuery = resolveQueryAlias(target, query, true);
@@ -2506,10 +2500,7 @@ void expandQueryActionTargetList(IProperties *queryIds, IPropertyTree *queryset,
 bool CWsWorkunitsEx::onWUQueryConfig(IEspContext &context, IEspWUQueryConfigRequest & req, IEspWUQueryConfigResponse & resp)
 {
     StringAttr target(req.getTarget());
-    if (target.isEmpty())
-        throw MakeStringException(ECLWATCH_MISSING_PARAMS, "Target name required");
-    if (!isValidCluster(target))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid target name: %s", target.get());
+    validateTargetName(target);
 
     Owned<IPropertyTree> queryset = getQueryRegistry(target.get(), false);
     if (!queryset)
@@ -3004,14 +2995,11 @@ bool CWsWorkunitsEx::onWUCopyQuerySet(IEspContext &context, IEspWUCopyQuerySetRe
     StringBuffer srcTarget;
     if (!splitQueryPath(source, srcAddress, srcTarget, NULL))
         throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid source target");
-    if (!srcAddress.length() && !isValidCluster(srcTarget))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid source target name: %s", source);
+    if (!srcTarget.isEmpty())
+        validateTargetName(srcTarget);
 
     const char *target = req.getTarget();
-    if (!target || !*target)
-        throw MakeStringException(ECLWATCH_MISSING_PARAMS, "No destination target specified");
-    if (!isValidCluster(target))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid destination target name: %s", target);
+    validateTargetName(target);
 
     DBGLOG("%s copying queryset %s from %s target %s", context.queryUserId(), target, srcAddress.str(), srcTarget.str());
 
@@ -3069,8 +3057,7 @@ bool CWsWorkunitsEx::onWUQuerysetCopyQuery(IEspContext &context, IEspWUQuerySetC
         throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid target queryset name");
     if (req.getCluster() && *req.getCluster() && !strieq(req.getCluster(), target)) //backward compatability check
         throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid target cluster and queryset must match");
-    if (!isValidCluster(target))
-        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid target name: %s", target);
+    validateTargetName(target);
 
     StringBuffer srcAddress, srcQuerySet, srcQuery;
     if (!splitQueryPath(source, srcAddress, srcQuerySet, &srcQuery))

+ 7 - 53
esp/services/ws_workunits/ws_workunitsService.cpp

@@ -357,7 +357,6 @@ void CWsWorkunitsEx::init(IPropertyTree *cfg, const char *process, const char *s
     DBGLOG("Initializing %s service [process = %s]", service, process);
 
     checkUpdateQuerysetLibraries();
-    refreshValidClusters();
 
     daliServers.set(cfg->queryProp("Software/EspProcess/@daliServers"));
     const char *computer = cfg->queryProp("Software/EspProcess/@computer");
@@ -451,44 +450,6 @@ void CWsWorkunitsEx::init(IPropertyTree *cfg, const char *process, const char *s
             cfg->getPropInt(xpath.str(), CHECK_QUERY_STATUS_THREAD_POOL_SIZE)));
 }
 
-void CWsWorkunitsEx::refreshValidClusters()
-{
-    validClusters.kill();
-#ifdef _CONTAINERIZED
-    // discovered from generated cluster names
-    Owned<IStringIterator> it = getContainerTargetClusters(nullptr, nullptr);
-#else
-    Owned<IStringIterator> it = getTargetClusters(nullptr, nullptr);
-#endif
-    ForEach(*it)
-    {
-        SCMStringBuffer s;
-        IStringVal &val = it->str(s);
-        bool* found = validClusters.getValue(val.str());
-        if (!found || !*found)
-        {
-            validClusters.setValue(val.str(), true);
-            PROGLOG("adding valid cluster: %s", val.str());
-        }
-    }
-}
-
-bool CWsWorkunitsEx::isValidCluster(const char *cluster)
-{
-    if (!cluster || !*cluster)
-        return false;
-    CriticalBlock block(crit);
-    bool* found = validClusters.getValue(cluster);
-    if (found && *found)
-        return true;
-    if (validateTargetClusterName(cluster))
-    {
-        refreshValidClusters();
-        return true;
-    }
-    return false;
-}
-
 bool CWsWorkunitsEx::onWUCreate(IEspContext &context, IEspWUCreateRequest &req, IEspWUCreateResponse &resp)
 {
     try
@@ -581,8 +542,7 @@ bool CWsWorkunitsEx::onWUUpdate(IEspContext &context, IEspWUUpdateRequest &req,
         {
             if (origValueChanged(req.getClusterSelection(), req.getClusterOrig(), s.clear(), false))
             {
-                if (!isValidCluster(s.str()))
-                    throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", s.str());
+                validateTargetName(s);
                 if (req.getState() == WUStateBlocked)
                     switchWorkUnitQueue(wu.get(), s.str());
                 else if ((req.getState() != WUStateSubmitted) && (req.getState() != WUStateRunning) && (req.getState() != WUStateDebugPaused) && (req.getState() != WUStateDebugRunning))
@@ -941,10 +901,7 @@ bool CWsWorkunitsEx::onWUSchedule(IEspContext &context, IEspWUScheduleRequest &r
         WsWuHelpers::checkAndTrimWorkunit("WUSchedule", wuid);
 
         const char* cluster = req.getCluster();
-        if (isEmpty(cluster))
-             throw MakeStringException(ECLWATCH_INVALID_INPUT,"No Cluster defined.");
-        if (!isValidCluster(cluster))
-            throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", cluster);
+        validateTargetName(cluster);
 
         Owned<IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
         WorkunitUpdate wu(factory->updateWorkUnit(wuid.str()));
@@ -996,10 +953,7 @@ bool CWsWorkunitsEx::onWUSubmit(IEspContext &context, IEspWUSubmitRequest &req,
         WsWuHelpers::checkAndTrimWorkunit("WUSubmit", wuid);
 
         const char *cluster = req.getCluster();
-        if (isEmpty(cluster))
-            throw MakeStringException(ECLWATCH_INVALID_INPUT,"No Cluster defined.");
-        if (!isValidCluster(cluster))
-            throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", cluster);
+        validateTargetName(cluster);
 
         Owned<IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
         Owned<IConstWorkUnit> cw = factory->openWorkUnit(wuid.str());
@@ -1056,8 +1010,8 @@ bool CWsWorkunitsEx::onWURun(IEspContext &context, IEspWURunRequest &req, IEspWU
     try
     {
         const char *cluster = req.getCluster();
-        if (notEmpty(cluster) && !isValidCluster(cluster))
-            throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", cluster);
+        if (!isEmptyString(cluster))
+            validateTargetName(cluster);
 
         StringBuffer wuidStr(req.getWuid());
         const char* runWuid = wuidStr.trim().str();
@@ -4845,8 +4799,8 @@ bool CWsWorkunitsEx::onWUDeployWorkunit(IEspContext &context, IEspWUDeployWorkun
     {
         ensureWsCreateWorkunitAccess(context);
 
-        if (notEmpty(req.getCluster()) && !isValidCluster(req.getCluster()))
-            throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid cluster name: %s", req.getCluster());
+        if (!isEmptyString(req.getCluster()))
+            validateTargetName(req.getCluster());
         if (!type || !*type)
             throw MakeStringExceptionDirect(ECLWATCH_INVALID_INPUT, "WUDeployWorkunit unspecified object type.");
         if (strieq(type, "archive")|| strieq(type, "ecl_text"))

+ 0 - 4
esp/services/ws_workunits/ws_workunitsService.hpp

@@ -222,8 +222,6 @@ public:
         CWsWorkunits::setContainer(container);
         m_sched.setContainer(container);
     }
-    void refreshValidClusters();
-    bool isValidCluster(const char *cluster);
     void deploySharedObjectReq(IEspContext &context, IEspWUDeployWorkunitRequest & req, IEspWUDeployWorkunitResponse & resp, const char *dir, const char *xml=NULL);
     unsigned getGraphIdsByQueryId(const char *target, const char *queryId, StringArray& graphIds);
     bool getQueryFiles(IEspContext &context, const char* wuid, const char* query, const char* target, StringArray& logicalFiles, IArrayOf<IEspQuerySuperFile> *superFiles);
@@ -412,8 +410,6 @@ private:
     Owned<WUArchiveCache> wuArchiveCache;
     StringAttr sashaServerIp;
     unsigned short sashaServerPort;
-    BoolHash validClusters;
-    CriticalSection crit;
     WUSchedule m_sched;
     unsigned short port;
     Owned<IPropertyTree> directories;

+ 70 - 0
esp/smc/SMCLib/TpWrapper.cpp

@@ -2228,3 +2228,73 @@ extern TPWRAPPER_API void initContainerRoxieTargets(MapStringToMyClass<ISmartSoc
     }
 }
 
+extern TPWRAPPER_API unsigned getThorClusterNames(StringArray& targetNames, StringArray& queueNames)
+{
+#ifndef _CONTAINERIZED
+    StringArray thorNames, groupNames;
+    getEnvironmentThorClusterNames(thorNames, groupNames, targetNames, queueNames);
+#else
+    Owned<IStringIterator> targets = getContainerTargetClusters("thor", nullptr);
+    ForEach(*targets)
+    {
+        SCMStringBuffer target;
+        targets->str(target);
+        targetNames.append(target.str());
+
+        StringBuffer qName;
+        queueNames.append(getClusterThorQueueName(qName, target.str()));
+    }
+#endif
+    return targetNames.ordinality();
+}
+
+static std::set<std::string> validTargets;
+static CriticalSection validTargetSect;
+static bool targetsDirty = true;
+
+static void refreshValidTargets()
+{
+    validTargets.clear();
+#ifdef _CONTAINERIZED
+    // discovered from generated cluster names
+    Owned<IStringIterator> it = getContainerTargetClusters(nullptr, nullptr);
+#else
+    Owned<IStringIterator> it = getTargetClusters(nullptr, nullptr);
+#endif
+    ForEach(*it)
+    {
+        SCMStringBuffer s;
+        IStringVal& val = it->str(s);
+        if (validTargets.find(val.str()) == validTargets.end())
+        {
+            validTargets.insert(val.str());
+            PROGLOG("adding valid target: %s", val.str());
+        }
+    }
+}
+
+extern TPWRAPPER_API void validateTargetName(const char* target)
+{
+    if (isEmptyString(target))
+        throw makeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Empty target name.");
+
+    CriticalBlock block(validTargetSect);
+    if (targetsDirty)
+    {
+        refreshValidTargets();
+        targetsDirty = false;
+    }
+
+    if (validTargets.find(target) != validTargets.end())
+        return;
+
+#ifdef _CONTAINERIZED
+    //Currently, if there's any change to the target queues, esp will be auto restarted by K8s.
+    throw makeStringExceptionV(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid target name: %s", target);
+#else
+    if (!validateTargetClusterName(target))
+        throw makeStringExceptionV(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid target name: %s", target);
+
+    targetsDirty = true;
+#endif
+}

+ 2 - 0
esp/smc/SMCLib/TpWrapper.hpp

@@ -217,6 +217,8 @@ extern TPWRAPPER_API IConstWUClusterInfo* getWUClusterInfoByName(const char* clu
 
 extern TPWRAPPER_API IStringIterator *getContainerTargetClusters(const char* processType, const char* processName);
 extern TPWRAPPER_API void initContainerRoxieTargets(MapStringToMyClass<ISmartSocketFactory>& connMap);
+extern TPWRAPPER_API unsigned getThorClusterNames(StringArray& targetNames, StringArray& queueNames);
+extern TPWRAPPER_API void validateTargetName(const char* target);
 
 #endif //_ESPWIZ_TpWrapper_HPP__