Kaynağa Gözat

HPCC-21424 Add envchk CLI to validate environment file

Add new CLI to run config manager core library validate function
and print results to stdout
Improve some XSDs to better detect invalid configurations
Improve config manager core library validation to remove
duplicate errors and improve error messages

Signed-off-by: Ken Rowland <kenneth.rowland@lexisnexisrisk.com>
Ken Rowland 6 yıl önce
ebeveyn
işleme
09af5197fc

+ 2 - 1
configuration/cli/CMakeLists.txt

@@ -1,5 +1,5 @@
 ################################################################################
-#    HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems.
+#    HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -15,3 +15,4 @@
 ################################################################################
 
 HPCC_ADD_SUBDIRECTORY (envmod)
+HPCC_ADD_SUBDIRECTORY (envchk)

+ 34 - 0
configuration/cli/envchk/CMakeLists.txt

@@ -0,0 +1,34 @@
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+################################################################################
+
+project ( envmod )
+
+SET (SRC_FILES
+        envchk.cpp
+        )
+
+INCLUDE_DIRECTORIES(
+        ${CMAKE_BINARY_DIR}
+        ${CMAKE_BINARY_DIR}/oss
+        ${HPCC_SOURCE_DIR}/system/include
+        ${HPCC_SOURCE_DIR}/configuration/configmgr/configmgrlib
+)
+
+HPCC_ADD_EXECUTABLE ( envchk ${SRC_FILES} )
+
+INSTALL ( TARGETS envchk RUNTIME DESTINATION ${EXEC_DIR} )
+
+TARGET_LINK_LIBRARIES(envchk configmgr)

+ 347 - 0
configuration/cli/envchk/envchk.cpp

@@ -0,0 +1,347 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2017 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#include <stdio.h>
+#include <iostream>
+#include <string>
+#include <vector>
+#include "build-config.h"
+#include "platform.h"
+#include <exception>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "EnvironmentMgr.hpp"
+#include "Exceptions.hpp"
+#include "Status.hpp"
+
+//
+// Inputs file format (json)
+//
+// {
+//   "input-name" : [ <array of values> ],
+//   ...
+// }
+
+bool processOptions(int argc, char *argv[]);
+bool validate();
+std::string getNextArg(int argc, char *argv[], int idx);
+bool dirExists(const std::string &dirName);
+bool fileExists(const std::string &fileName);
+std::vector<std::string> splitString(const std::string &input, const std::string &delim);
+void outputStatus(Status &status, enum statusMsg::msgLevel);
+
+// Default configuration directories
+EnvironmentType envType = XML;
+std::string masterSchemaFile = "environment.xsd";
+std::string configSchemaDir = COMPONENTFILES_DIR PATHSEPSTR "configschema" PATHSEPSTR "xsd" PATHSEPSTR;
+std::string configSchemaPluginsDir = "xsd" PATHSEPSTR "plugins" PATHSEPSTR;
+std::string envFile;
+enum statusMsg::msgLevel outputLevel = statusMsg::error;
+bool verbose = false;
+bool validateHiddenNodes = false;
+
+
+EnvironmentMgr *pEnvMgr = nullptr;
+
+class CliException : public std::exception
+{
+    public:
+
+        explicit CliException(std::string reason) :  m_reason(std::move(reason)) { }
+        virtual const char *what() const noexcept override
+        {
+            return m_reason.c_str();
+        }
+
+    private:
+
+        std::string m_reason;
+};
+
+
+void usage()
+{
+    //
+    // usage below documents options
+    std::cout << std::endl;
+    std::cout << "envchk <options> envfile" << std::endl;
+    std::cout << std::endl;
+    std::cout << "    -d --schema-dir <path>            : path to schema files. default (" << configSchemaDir << ")" << std::endl;
+    std::cout << "    -p --schema-plugins-dir <path>    : path to plugin files, default (" << configSchemaPluginsDir << ")" << std::endl;
+    std::cout << "    -m --master-config <filename>     : name of master schema file, default (" << masterSchemaFile << ")" << std::endl;
+    std::cout << "    -l --level <level>                : output level (and above), e=error, w=warning, i=informational" << std::endl;
+    std::cout << "    -v --verbose                      : verbose output" << std::endl;
+    std::cout << std::endl;
+}
+
+
+int main(int argc, char *argv[])
+{
+    int rc = 0;
+    if (argc == 1)
+    {
+        usage();
+        return 0;
+    }
+
+    //
+    // Read options and validate
+    if (!processOptions(argc, argv))
+    {
+        return 1;   // get out now
+    }
+
+    if (!validate())
+    {
+        usage();
+        return 1;
+    }
+
+    //
+    // Create an environment manager reference and load the schema
+    try
+    {
+        pEnvMgr = getEnvironmentMgrInstance(envType);
+        if (verbose)
+            std::cout << "Loading schema defined by " << masterSchemaFile << std::endl;
+
+        // note that these are hardcoded for HPCC at this time, but could be made into options
+        std::map<std::string, std::string> cfgParms;
+        cfgParms["buildset"] = "buildset.xml";  // Not used right now, and probably never will be
+        //cfgParms["support_libs"] = "libcfgsupport_addrequiredinstances";
+        std::string pluginsPath = configSchemaPluginsDir;
+
+        if (!pEnvMgr->loadSchema(configSchemaDir, masterSchemaFile, cfgParms))
+        {
+            throw CliException(pEnvMgr->getLastSchemaMessage());
+        }
+
+    }
+    catch (const ParseException &pe)
+    {
+        std::cout << "There was a problem creating the environment manager instance: " << pe.what() << std::endl;
+    }
+    catch(const CliException &e)
+    {
+        std::cout << "There was a problem loading the schema: " << e.what() << std::endl;
+    }
+
+    //
+    // If there is an environment, load it and apply
+    if (!envFile.empty())
+    {
+        if (pEnvMgr->loadEnvironment(envFile))
+        {
+            Status status;
+            pEnvMgr->validate(status, validateHiddenNodes);
+            outputStatus(status, outputLevel);
+        }
+        else
+        {
+            std::cout << "There was a problem loading the environment: " << std::endl << pEnvMgr->getLastEnvironmentMessage() << std::endl;
+            rc = 1;
+        }
+    }
+
+    return rc;
+}
+
+
+void outputStatus(Status &status, enum statusMsg::msgLevel lvl)
+{
+    int startLevel = status.getHighestMsgLevel();
+    auto msgs = status.getMessages(lvl);
+    for (auto &msg: msgs)
+    {
+        std::string path;
+        if (!msg.nodeId.empty())
+        {
+            auto pNode = pEnvMgr->findEnvironmentNodeById(msg.nodeId);
+            if (pNode)
+            {
+                pNode->getPath(path);
+                std::cout << status.getStatusTypeString(msg.msgLevel) << " - Path: " << path;
+                if (!msg.attribute.empty())
+                    std::cout << "[" << msg.attribute << "]";
+                std::cout << " Message: " << msg.msg << std::endl;
+            }
+        }
+    }
+}
+
+
+bool processOptions(int argc, char *argv[])
+{
+    bool rc = true;
+    int idx = 1;
+    std::string optName, optVal;
+    bool checkDir = false;
+
+    try
+    {
+        while (idx < argc)
+        {
+            optName = getNextArg(argc, argv, idx++);
+
+            if (optName == "-d" || optName == "--schema-dir")
+            {
+                configSchemaDir = getNextArg(argc, argv, idx++) += PATHSEPSTR;
+            }
+
+            else if (optName == "-p" || optName == "--schema-plugins-dir")
+            {
+                configSchemaPluginsDir = getNextArg(argc, argv, idx++) += PATHSEPSTR;
+            }
+
+            else if (optName == "-m" || optName == "--master-config")
+            {
+                masterSchemaFile = getNextArg(argc, argv, idx++);
+            }
+
+            else if (optName == "-l" || optName == "--level")
+            {
+                std::string lvl = getNextArg(argc, argv, idx++);
+                {
+                    if (lvl == "e")
+                    {
+                        outputLevel = statusMsg::error;
+                    }
+                    else if (lvl == "w")
+                    {
+                        outputLevel = statusMsg::warning;
+                    }
+                    else if (lvl == "i")
+                    {
+                        outputLevel = statusMsg::info;
+                    }
+                    else
+                    {
+                        throw CliException("Output level is not valid");
+                    }
+                }
+            }
+
+            else if (optName == "-v" || optName == "--verbose")
+            {
+                verbose = true;
+            }
+
+            else if (idx == argc)
+            {
+                envFile = optName;
+            }
+
+            else
+            {
+                throw CliException("Parameters are incorrect");
+            }
+        }
+    }
+    catch(const CliException &e)
+    {
+        std::cout << "There was an issue processing options: " << e.what() << std::endl;
+        rc = false;
+    }
+    return rc;
+}
+
+
+bool validate()
+{
+
+    if (!dirExists(configSchemaDir))
+    {
+        std::cout << "Schema directory " << configSchemaDir << " does not exist" << std::endl;
+        return false;
+    }
+
+    if (!dirExists(configSchemaPluginsDir))
+    {
+        std::cout << "Schema plugins directory " << configSchemaPluginsDir << " does not exist" << std::endl;
+        return false;
+    }
+
+    if (!fileExists(configSchemaDir + masterSchemaFile))
+    {
+        std::cout << "The master config file " << masterSchemaFile << " does not exist" << std::endl;
+        return false;
+    }
+
+    if (!envFile.empty())
+    {
+        if (!fileExists(envFile))
+        {
+            std::cout << "The environment file " << envFile << " does not exist" << std::endl;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+std::string getNextArg(int argc, char *argv[], int idx)
+{
+    if (idx < argc)
+    {
+        return std::string(argv[idx]);
+    }
+    throw CliException("Arguments exhausted when more expected");
+}
+
+
+bool dirExists(const std::string &dirName)
+{
+    bool rc = true;
+    struct stat info;
+    if (stat(dirName.c_str(), &info) != 0)
+    {
+        rc = false;
+    }
+    rc = ((info.st_mode&S_IFMT)==S_IFDIR);
+    return rc;
+}
+
+
+bool fileExists(const std::string &fileName)
+{
+    struct stat info;
+    return stat(fileName.c_str(), &info) == 0;
+}
+
+
+std::vector<std::string> splitString(const std::string &input, const std::string &delim)
+{
+    size_t  start = 0, end = 0, delimLen = delim.length();
+    std::vector<std::string> list;
+
+    while (end != std::string::npos)
+    {
+        end = input.find(delim, start);
+        std::string item = input.substr(start, (end == std::string::npos) ? std::string::npos : end - start);
+        if (!item.empty())
+            list.push_back(item);
+
+        if (end != std::string::npos)
+        {
+            start = end + delimLen;
+            if (start >= input.length())
+                end = std::string::npos;
+        }
+    }
+    return list;
+}

+ 5 - 1
configuration/cli/envmod/envmod.cpp

@@ -1,9 +1,13 @@
 /*##############################################################################
-    HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®.
+
+    HPCC SYSTEMS software Copyright (C) 2017 HPCC Systems®.
+
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
+
        http://www.apache.org/licenses/LICENSE-2.0
+
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ 11 - 20
configuration/configmgr/configmgrlib/EnvironmentNode.cpp

@@ -233,26 +233,6 @@ void EnvironmentNode::validate(Status &status) const
         attrIt.second->validate(status, m_id);
 
         //
-        // If this value must be unique, make sure it is
-        if (attrIt.second->getSchemaValue()->isUniqueValue())
-        {
-            bool found = false;
-            std::vector<std::string> allValues;
-            attrIt.second->getAllValuesForSiblings(allValues);
-            std::set<std::string> unquieValues;
-            for (auto it = allValues.begin(); it != allValues.end() && !found; ++it)
-            {
-                auto ret = unquieValues.insert(*it);
-                found = !ret.second;
-            }
-
-            if (found)
-            {
-                status.addUniqueMsg(statusMsg::error, m_id, attrIt.second->getName(), "Attribute value must be unique");
-            }
-        }
-
-        //
         // Does this value need to be from another set of values?
         if (attrIt.second->getSchemaValue()->isFromUniqueValueSet())
         {
@@ -478,6 +458,17 @@ void EnvironmentNode::doFetchNodes(ConfigPath &configPath, std::vector<std::shar
 }
 
 
+void EnvironmentNode::getPath(std::string &path) const
+{
+    path = m_name + path;
+    path = "/" + path;
+    if (!m_pParent.expired())
+    {
+        m_pParent.lock()->getPath(path);
+    }
+}
+
+
 std::shared_ptr<EnvironmentNode> EnvironmentNode::getRoot() const
 {
     std::shared_ptr<EnvironmentNode> pParent = m_pParent.lock();

+ 1 - 0
configuration/configmgr/configmgrlib/EnvironmentNode.hpp

@@ -69,6 +69,7 @@ class CFGMGRLIB_API EnvironmentNode : public std::enable_shared_from_this<Enviro
         void getInsertableItems(std::vector<InsertableItem> &items) const;
         void initialize();
         void fetchNodes(const std::string &path, std::vector<std::shared_ptr<EnvironmentNode>> &nodes) const;
+        void getPath(std::string &path) const;
         std::shared_ptr<EnvironmentNode> getRoot() const;
         void addEnvironmentInsertData(const std::string &envData) { m_insertData = envData; }
         const std::string &getEnvironmentInsertData() const { return m_insertData; }

+ 91 - 56
configuration/configmgr/configmgrlib/SchemaItem.cpp

@@ -37,6 +37,14 @@ SchemaItem::SchemaItem(const std::string &name, const std::string &className, co
     m_properties["className"] = className;
 
     //
+    // If this is a default schema item, allow any number of them
+    if (className == "default")
+    {
+        m_minInstances = 0;
+        m_maxInstances = UINT_MAX;
+    }
+
+    //
     // If this is a root node (no parent), then do some additional init
     if (m_pParent.expired())
     {
@@ -652,70 +660,99 @@ void SchemaItem::setRequiredInstanceComponents(const std::string list)
 void SchemaItem::validate(Status &status, bool includeChildren, bool includeHiddenNodes) const
 {
     //
-    // Check the number of environment nodes against the allowed min/max range. Note that since the schema does not
-    // maintain a parent child relationship like the environment, the environment nodes for this schema item need to
-    // be separated by each environment node's parent. Then the number of nodes, by parent, is compared agains the
-    // allowed min/max range
-    std::map<std::string, std::vector<std::shared_ptr<EnvironmentNode>>> envNodesByParent;
-
-    //
-    // Fill in the nodes by parent so that we know how many of this schema item are under each parent. Note some will
-    // be 0 and this needs to be known in case the min number is not 0. If there is not parent for this item, then we
-    // are at the root and no check is necessary (expired check OK since the schema is static once loaded)
-    if (!m_pParent.expired())
+    // If the schema is not hiding the node or we want hidden nodes, validate it
+    if (!isHidden() || includeHiddenNodes)
     {
-        auto pParentItem = m_pParent.lock();  // also, schema items are not deleted, so this will always be good
-        for (auto &pParentWeakEnvNode: pParentItem->m_envNodes)
+        //
+        // Check the number of environment nodes against the allowed min/max range. Note that since the schema does not
+        // maintain a parent child relationship like the environment, the environment nodes for this schema item need to
+        // be separated by each environment node's parent. Then the number of nodes, by parent, is compared agains the
+        // allowed min/max range
+        std::map<std::string, std::vector<std::shared_ptr<EnvironmentNode>>> envNodesByParent;
+
+        //
+        // Fill in the nodes by parent so that we know how many of this schema item are under each parent. Note some will
+        // be 0 and this needs to be known in case the min number is not 0. If there is not parent for this item, then we
+        // are at the root and no check is necessary (expired check OK since the schema is static once loaded)
+        if (!m_pParent.expired())
         {
-            std::shared_ptr<EnvironmentNode> pParentEnvNode = pParentWeakEnvNode.lock();
-            if (pParentEnvNode)
+            auto pParentItem = m_pParent.lock();  // also, schema items are not deleted, so this will always be good
+            for (auto &pParentWeakEnvNode: pParentItem->m_envNodes)
             {
-                envNodesByParent[pParentEnvNode->getId()] = std::vector<std::shared_ptr<EnvironmentNode>>();
+                std::shared_ptr<EnvironmentNode> pParentEnvNode = pParentWeakEnvNode.lock();
+                if (pParentEnvNode)
+                {
+                    envNodesByParent[pParentEnvNode->getId()] = std::vector<std::shared_ptr<EnvironmentNode>>();
+                }
             }
         }
-    }
 
-    //
-    // Now build the vector per parent node ID
-    for (auto &pWeakEnvNode: m_envNodes)
-    {
-        std::shared_ptr<EnvironmentNode> pEnvNode = pWeakEnvNode.lock();
-        if (pEnvNode)
+        //
+        // Now build the vector per parent node ID
+        for (auto &pWeakEnvNode: m_envNodes)
         {
-            std::shared_ptr<EnvironmentNode> pParentEnvNode = pEnvNode->getParent();
-            if (pParentEnvNode)
+            std::shared_ptr<EnvironmentNode> pEnvNode = pWeakEnvNode.lock();
+            if (pEnvNode)
             {
-                envNodesByParent[pParentEnvNode->getId()].emplace_back(pEnvNode);
+                std::shared_ptr<EnvironmentNode> pParentEnvNode = pEnvNode->getParent();
+                if (pParentEnvNode)
+                {
+                    envNodesByParent[pParentEnvNode->getId()].emplace_back(pEnvNode);
+                }
             }
         }
-    }
 
-    //
-    // Now validate the count of nodes
-    for (auto &envNodes: envNodesByParent)
-    {
-        if (envNodes.second.size() < m_minInstances || envNodes.second.size() > m_maxInstances)
+        //
+        // Now validate the count of nodes
+        for (auto &envNodes: envNodesByParent)
         {
-            for (auto &pEnvNode: envNodes.second)
+            if (envNodes.second.size() < m_minInstances || envNodes.second.size() > m_maxInstances)
             {
-                std::string path;
-                getPath(path);
-                std::string msg;
-                msg = "The number of SchemaItem " + path + " nodes (" + std::to_string(envNodes.second.size()) +
-                      ") is outside the range of " + std::to_string(m_minInstances) +
-                      " to " + std::to_string(m_maxInstances);
-                status.addMsg(statusMsg::error, pEnvNode->getId(), "", msg);
+                for (auto &pEnvNode: envNodes.second)
+                {
+                    std::string path;
+                    getPath(path);
+                    std::string msg;
+                    msg = "The number of " + path + " nodes (" + std::to_string(envNodes.second.size()) +
+                          ") is outside the range of " + std::to_string(m_minInstances) +
+                          " to " + std::to_string(m_maxInstances);
+                    status.addMsg(statusMsg::error, pEnvNode->getId(), "", msg);
+                }
             }
         }
-    }
 
 
-    //
-    // Now validate each environment node (at this time go ahead and reset the member variable of weak pointers as well
-    // to get rid of any stale values)
-    for (auto &pWeakEnvNode: m_envNodes)
-    {
-        if (!isHidden() || includeHiddenNodes)
+        //
+        // Check the attributes for uniqueness.
+        for (auto &attribute: m_attributes)
+        {
+            if (attribute.second->isUniqueValue())
+            {
+                std::vector<std::shared_ptr<EnvironmentValue>> envValues;
+                std::set<std::string> unquieValues;
+                attribute.second->getAllEnvironmentValues(envValues);
+                bool found = false;
+                for (auto it = envValues.begin(); it != envValues.end() && !found; ++it)
+                {
+                    auto ret = unquieValues.insert((*it)->getValue());
+                    found = !ret.second;
+                    if (found)
+                    {
+                        std::string path;
+                        getPath(path);
+                        status.addUniqueMsg(statusMsg::error, (*it)->getEnvironmentNode()->getId(),
+                                            attribute.second->getName(),
+                                            "Attribute value (" + (*it)->getValue() + ") must be unique");
+                    }
+                }
+
+            }
+        }
+
+        //
+        // Now validate each environment node (at this time go ahead and reset the member variable of weak pointers as well
+        // to get rid of any stale values)
+        for (auto &pWeakEnvNode: m_envNodes)
         {
             std::shared_ptr<EnvironmentNode> pEnvNode = pWeakEnvNode.lock();
             if (pEnvNode)
@@ -723,17 +760,15 @@ void SchemaItem::validate(Status &status, bool includeChildren, bool includeHidd
                 pEnvNode->validate(status);
             }
         }
-    }
-
 
-    //
-    // Validate children, if so indicated
-    if (includeChildren)
-    {
-        for (auto &pSchemaChild: m_children)
+        //
+        // Validate children, if so indicated
+        if (includeChildren)
         {
-            pSchemaChild->validate(status, includeChildren, includeHiddenNodes);
+            for (auto &pSchemaChild: m_children)
+            {
+                pSchemaChild->validate(status, includeChildren, includeHiddenNodes);
+            }
         }
     }
-
 }

+ 18 - 4
configuration/configmgr/configmgrlib/SchemaTypeStringLimits.cpp

@@ -17,11 +17,23 @@
 
 #include "SchemaTypeStringLimits.hpp"
 #include "EnvironmentValue.hpp"
-#include "jregexp.hpp"
+#include <regex>
+
+#if __cplusplus >= 201103L &&                             \
+    (!defined(__GLIBCXX__) || (__cplusplus >= 201402L) || \
+        (defined(_GLIBCXX_REGEX_DFS_QUANTIFIERS_LIMIT) || \
+         defined(_GLIBCXX_REGEX_STATE_LIMIT)           || \
+             (defined(_GLIBCXX_RELEASE)                && \
+             _GLIBCXX_RELEASE > 4)))
+#define HAVE_WORKING_REGEX 1
+#else
+#define HAVE_WORKING_REGEX 0
+#endif
+
 
 std::string SchemaTypeStringLimits::getLimitString() const
 {
-    return "String limit info";
+    return "";
 }
 
 
@@ -32,10 +44,12 @@ bool SchemaTypeStringLimits::doValueTest(const std::string &testValue) const
     isValid = len >= m_minLength && len <= m_maxLength;
 
     // test patterns
+#if HAVE_WORKING_REGEX
     for (auto pattern = m_patterns.begin(); isValid && pattern != m_patterns.end(); ++pattern)
     {
-        RegExpr expr(pattern->c_str());
-        isValid = expr.find(testValue.c_str()) != nullptr;
+        std::regex r(pattern->c_str());
+        isValid = std::regex_match(testValue.c_str(), r);
     }
+#endif
     return isValid;
 }

+ 5 - 1
configuration/configmgr/configmgrlib/SchemaValue.cpp

@@ -210,7 +210,11 @@ void SchemaValue::getAllEnvironmentValues(std::vector<std::shared_ptr<Environmen
 {
     for (auto it = m_envValues.begin(); it != m_envValues.end(); ++it)
     {
-        envValues.push_back(it->lock());
+        std::shared_ptr<EnvironmentValue> pEnvValue = it->lock();
+        if (pEnvValue)
+        {
+            envValues.push_back(pEnvValue);
+        }
     }
 }
 

+ 8 - 3
configuration/configmgr/configmgrlib/Status.cpp

@@ -56,12 +56,17 @@ void Status::addUniqueMsg(enum statusMsg::msgLevel level, const std::string &nod
         addMsg(level, nodeId, name, msg);
 }
 
-std::vector<statusMsg> Status::getMessages() const
+
+std::vector<statusMsg> Status::getMessages(enum statusMsg::msgLevel level) const
 {
     std::vector<statusMsg> msgs;
-    for (auto it = m_messages.begin(); it != m_messages.end(); ++it)
+    for (int curLvl = level; curLvl >= statusMsg::info; --curLvl)
     {
-        msgs.push_back(it->second);
+        auto msgRange = m_messages.equal_range(static_cast<enum statusMsg::msgLevel>(curLvl));
+        for (auto msgIt = msgRange.first; msgIt != msgRange.second; ++msgIt)
+        {
+            msgs.emplace_back(msgIt->second);
+        }
     }
     return msgs;
 }

+ 1 - 1
configuration/configmgr/configmgrlib/Status.hpp

@@ -61,7 +61,7 @@ class CFGMGRLIB_API Status
         bool isError() const { return m_highestMsgLevel >= statusMsg::error; }
         std::string getStatusTypeString(enum statusMsg::msgLevel status) const;
         enum statusMsg::msgLevel getMsgLevelFromString(const std::string &status) const;
-        std::vector<statusMsg> getMessages() const;
+        std::vector<statusMsg> getMessages(enum statusMsg::msgLevel level = statusMsg::fatal) const;
         void add(const std::vector<statusMsg> msgs);
 
 

+ 7 - 1
configuration/configmgr/configmgrlib/Utils.cpp

@@ -32,7 +32,13 @@ std::vector<std::string> splitString(const std::string &input, const std::string
         std::string item = input.substr(start, (end == std::string::npos) ? std::string::npos : end - start);
         if (!item.empty())
             list.push_back(item);
-        start = ((end > (std::string::npos - delimLen)) ? std::string::npos : end + delimLen);
+
+        if (end != std::string::npos)
+        {
+            start = end + delimLen;
+            if (start >= input.length())
+                end = std::string::npos;
+        }
     }
     return list;
 }

+ 3 - 0
initfiles/componentfiles/configschema/xsd/roxie.xsd

@@ -95,6 +95,9 @@
                     <xs:attribute name="defaultSLAPriorityTimeWarning" type="xs:nonNegativeInteger"
                                   hpcc:displayName="Default SLA Priority Time Warning (ms)" hpcc:presetValue="5000"
                                   hpcc:tooltip="Time (ms) before generating SNMP warning for a SLA-high-priority query (if not overriden)"/>
+                    <!-- defaultStripLeadingWhitespace my present a problem as xs:boolean (as it was in the previous XSD. The previous XSD
+                         gave a default value of "1", which is NOT compliant for an xs:boolean type which expects "true" or "false". Perhaps this
+                         should be changed? Or, should we recognize "1" and "0" as allowed xs:boolean values when validating -->
                     <xs:attribute name="defaultStripLeadingWhitespace" type="xs:boolean"
                                   hpcc:displayName="Default Strip Leading Whitespace" hpcc:presetValue="true"
                                   hpcc:tooltip="Default value for stripping leading whitespace in input XML values"/>

+ 1 - 1
initfiles/componentfiles/configschema/xsd/topology.xsd

@@ -23,7 +23,7 @@
         <xs:element name="Topology" hpcc:displayName="Topology" hpcc:itemType="topology" minOccurs="0" maxOccurs="unbounded" hpcc:class="component">
             <xs:complexType>
                 <xs:sequence>
-                    <xs:element name="Cluster">
+                    <xs:element name="Cluster" minOccurs="0" maxOccurs="unbounded">
                         <xs:complexType>
                             <xs:sequence>
                                 <xs:element name="EclAgentProcess" hpcc:displayName="ECL Agent Process" hpcc:itemType="topology_eclagentprocess" hpcc:class="elementSet"

+ 4 - 3
initfiles/componentfiles/configschema/xsd/types.xsd

@@ -47,20 +47,21 @@
     <xs:simpleType name="cronSchedule">
         <xs:restriction base="xs:string">
             <xs:pattern
-                    value="^(\*|((\*\/)?[1-5]?[0-9])) (\*|((\*\/)?[1-5]?[0-9])) (\*|((\*\/)?(1?[0-9]|2[0-3]))) (\*|((\*\/)?([1-9]|[12][0-9]|3[0-1]))) (\*|((\*\/)?([1-9]|1[0-2]))) (\*|((\*\/)?[0-6]))$"/>
+                    value="^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$"/>
+                    <!--value="^(\*|((\*\/)?[1-5]?[0-9])) (\*|((\*\/)?[1-5]?[0-9])) (\*|((\*\/)?(1?[0-9]|2[0-3]))) (\*|((\*\/)?([1-9]|[12][0-9]|3[0-1]))) (\*|((\*\/)?([1-9]|1[0-2]))) (\*|((\*\/)?[0-6]))$"/>-->
         </xs:restriction>
     </xs:simpleType>
 
     <xs:simpleType name="ipV4Address">
         <xs:restriction base="xs:string">
             <xs:pattern
-                    value="\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z"/>
+                    value="^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|[.]$"/>
         </xs:restriction>
     </xs:simpleType>
 
     <xs:simpleType name="absolutePath">
         <xs:restriction base="xs:string">
-            <xs:pattern value="/^\\/.*/"/>
+            <!--<xs:pattern value="/^\\/.*/"/>-->
         </xs:restriction>
     </xs:simpleType>