Ver código fonte

HPCC-9123 Copy PackageMap from one target to another

Signed-off-by: Anthony Fishbeck <anthony.fishbeck@lexisnexis.com>
Anthony Fishbeck 8 anos atrás
pai
commit
31c8b3c57a

+ 3 - 0
common/workunit/referencedfilelist.cpp

@@ -616,6 +616,9 @@ void ReferencedFileList::addFilesFromPackageMap(IPropertyTree *pm)
     Owned<IPropertyTreeIterator> packages = pm->getElements("Package");
     ForEach(*packages)
         addFilesFromPackage(packages->query(), ip, cluster, prefix);
+    packages.setown(pm->getElements("Part/Package"));
+    ForEach(*packages)
+        addFilesFromPackage(packages->query(), ip, cluster, prefix);
 }
 
 bool ReferencedFileList::addFilesFromQuery(IConstWorkUnit *cw, const IHpccPackage *pkg)

+ 150 - 0
ecl/ecl-package/ecl-package.cpp

@@ -647,6 +647,153 @@ private:
     bool optPreloadAll;
 };
 
+class EclCmdPackageMapCopy : public EclCmdCommon
+{
+public:
+    EclCmdPackageMapCopy()
+    {
+    }
+    virtual eclCmdOptionMatchIndicator parseCommandLineOptions(ArgvIterator &iter) override
+    {
+        if (iter.done())
+            return EclCmdOptionNoMatch;
+
+        for (; !iter.done(); iter.next())
+        {
+            const char *arg = iter.query();
+            if (*arg!='-')
+            {
+                if (optSrcPath.isEmpty())
+                    optSrcPath.set(arg);
+                else if (optTarget.isEmpty())
+                    optTarget.set(arg);
+                else
+                {
+                    fprintf(stderr, "\nunrecognized argument %s\n", arg);
+                    return EclCmdOptionNoMatch;
+                }
+                continue;
+            }
+            if (iter.matchOption(optDaliIP, ECLOPT_DALIIP))
+                continue;
+            if (iter.matchOption(optPMID, ECLOPT_PMID))
+                continue;
+            if (iter.matchOption(optSourceProcess, ECLOPT_SOURCE_PROCESS))
+                continue;
+            if (iter.matchFlag(optActivate, ECLOPT_ACTIVATE)||iter.matchFlag(optActivate, ECLOPT_ACTIVATE_S))
+                continue;
+            if (iter.matchFlag(optReplacePackagemap, ECLOPT_REPLACE))
+                continue;
+            if (iter.matchFlag(optUpdateSuperfiles, ECLOPT_UPDATE_SUPER_FILES))
+                continue;
+            if (iter.matchFlag(optUpdateCloneFrom, ECLOPT_UPDATE_CLONE_FROM))
+                continue;
+            if (iter.matchFlag(optDontAppendCluster, ECLOPT_DONT_APPEND_CLUSTER))
+                continue;
+            if (iter.matchFlag(optPreloadAll, ECLOPT_PRELOAD_ALL_PACKAGES))
+                continue;
+            eclCmdOptionMatchIndicator ind = EclCmdCommon::matchCommandLineOption(iter, true);
+            if (ind != EclCmdOptionMatch)
+                return ind;
+        }
+        return EclCmdOptionMatch;
+    }
+    virtual bool finalizeOptions(IProperties *globals) override
+    {
+        if (!EclCmdCommon::finalizeOptions(globals))
+        {
+            usage();
+            return false;
+        }
+        StringBuffer err;
+        if (optSrcPath.isEmpty())
+            err.append("\n ... Missing path to source packagemap\n");
+        else if (optTarget.isEmpty())
+            err.append("\n ... Specify a target cluster\n");
+
+        if (err.length())
+        {
+            fputs(err.str(), stderr);
+            return false;
+        }
+
+        return true;
+    }
+    virtual int processCMD() override
+    {
+        Owned<IClientWsPackageProcess> packageProcessClient = createCmdClient(WsPackageProcess, *this);
+
+        fprintf(stdout, "\n ... copy package map %s to %s\n\n", optSrcPath.str(), optTarget.str());
+
+        Owned<IClientCopyPackageMapRequest> request = packageProcessClient->createCopyPackageMapRequest();
+        request->setSourcePath(optSrcPath);
+        request->setTarget(optTarget);
+        request->setProcess("*");
+        request->setPMID(optPMID);
+        request->setActivate(optActivate);
+        request->setDaliIp(optDaliIP);
+        request->setSourceProcess(optSourceProcess);
+        request->setPreloadAllPackages(optPreloadAll);
+        request->setReplacePackageMap(optReplacePackagemap);
+        request->setUpdateSuperFiles(optUpdateSuperfiles);
+        request->setUpdateCloneFrom(optUpdateCloneFrom);
+        request->setAppendCluster(!optDontAppendCluster);
+
+        Owned<IClientCopyPackageMapResponse> resp = packageProcessClient->CopyPackageMap(request);
+        int ret = outputMultiExceptionsEx(resp->getExceptions());
+
+        StringArray &notFound = resp->getFilesNotFound();
+        if (notFound.length())
+        {
+            fputs("\nFiles defined in package but not found in DFS:\n", stderr);
+            ForEachItemIn(i, notFound)
+                fprintf(stderr, "  %s\n", notFound.item(i));
+            fputs("\n", stderr);
+        }
+
+        return ret;
+    }
+
+    virtual void usage() override
+    {
+        fputs("\nUsage:\n"
+                    "\n"
+                    "The 'copy' command will copy a package map from one target to another \n"
+                    "\n"
+                    "ecl packagemap copy <path> <target>\n"
+                    "   <path>                 Path to the source packagemap to copy\n"
+                    "                          The following formats are supported:\n"
+                    "                            remote PackageMap - //IP:PORT/Target/PackageMapId\n"
+                    "                            local PackageMap - target/PackageMapId\n"
+                    "   <target>               Name of target to copy the packagemap to\n"
+                    " Options:\n"
+                    "   -A, --activate         Activate the package information\n"
+                    "   --daliip=<ip>          IP of the remote dali to use for logical file lookups\n"
+                    "   --pmid                 Identifier of package map - defaults to source PMID\n"
+                    "   --source-process       Process cluster to copy files from\n"
+                    "   --preload-all          Set preload files option for all packages\n"
+                    "   --replace              Replace existing packagmap\n"
+                    "   --update-super-files   Update local DFS super-files if remote DALI has changed\n"
+                    "   --update-clone-from    Update local clone from location if remote DALI has changed\n"
+                    "   --dont-append-cluster  Only use to avoid locking issues due to adding cluster to file\n",
+                    stdout);
+
+        EclCmdCommon::usage();
+    }
+private:
+    StringAttr optSrcPath;
+    StringAttr optTarget;
+    StringAttr optPMID;
+    StringAttr optDaliIP;
+    StringAttr optSourceProcess;
+    bool optActivate = false;
+    bool optReplacePackagemap = false;
+    bool optUpdateSuperfiles = false;
+    bool optUpdateCloneFrom = false;
+    bool optDontAppendCluster = false; //Undesirable but here temporarily because DALI may have locking issues
+    bool optPreloadAll = false;
+};
+
 class EclCmdPackageValidate : public EclCmdCommon
 {
 public:
@@ -1373,6 +1520,8 @@ IEclCommand *createPackageSubCommand(const char *cmdname)
         return NULL;
     if (strieq(cmdname, "add"))
         return new EclCmdPackageAdd();
+    if (strieq(cmdname, "copy"))
+        return new EclCmdPackageMapCopy();
     if (strieq(cmdname, "delete"))
         return new EclCmdPackageDelete();
     if (strieq(cmdname, "activate"))
@@ -1412,6 +1561,7 @@ public:
             "ecl packagemap <command> [command options]\n\n"
             "   packagemap Commands:\n"
             "      add          Add a package map to the environment\n"
+            "      copy         Copy a package map from one target to another\n"
             "      delete       Delete a package map\n"
             "      activate     Activate a package map\n"
             "      deactivate   Deactivate a package map (package map will not get loaded)\n"

+ 4 - 0
ecl/eclcc/eclcc.cpp

@@ -482,6 +482,10 @@ int main(int argc, const char *argv[])
     InitModuleObjects();
     queryStderrLogMsgHandler()->setMessageFields(0);
 
+    StringBuffer prompt("continue: ");
+    StringBuffer pw;
+    passwordInput(prompt, pw);
+
     // Turn logging down (we turn it back up if -v option seen)
     Owned<ILogMsgFilter> filter = getCategoryLogMsgFilter(MSGAUD_user, MSGCLS_error);
     queryLogMsgManager()->changeMonitorFilter(queryStderrLogMsgHandler(), filter);

+ 27 - 1
esp/scm/ws_packageprocess.ecm

@@ -51,6 +51,31 @@ ESPresponse [exceptions_inline] AddPackageResponse
     ESParray<string, File> FilesNotFound;
 };
 
+ESPrequest CopyPackageMapRequest
+{
+    string SourcePath;
+    string RemoteUserName;
+    [password] string RemotePassword;
+    string Target;
+    string Process;
+    string PMID;
+    boolean Activate;
+    string DaliIp;
+    bool GlobalScope(0);
+    string SourceProcess;
+    bool PreloadAllPackages(false);
+    bool ReplacePackageMap(false);
+    bool UpdateSuperFiles(false); //usually wouldn't be needed, packagemap referencing superfiles?
+    bool UpdateCloneFrom(false); //explicitly wan't to change where roxie will grab from
+    bool AppendCluster(true); //file exists on other local cluster, add new one, make optional in case of locking issues, but should be made to work
+};
+
+ESPresponse [exceptions_inline] CopyPackageMapResponse
+{
+    ESPstruct BasePackageStatus status;
+    ESParray<string, File> FilesNotFound;
+};
+
 ESPrequest DeletePackageRequest
 {
     string Target;
@@ -288,10 +313,11 @@ ESPresponse [exceptions_inline] GetPartFromPackageMapResponse
     string Content;
 };
 
-ESPservice [auth_feature("NONE"), version("1.02"), exceptions_inline("./smc_xslt/exceptions.xslt")] WsPackageProcess
+ESPservice [auth_feature("NONE"), version("1.03"), exceptions_inline("./smc_xslt/exceptions.xslt")] WsPackageProcess
 {
     ESPmethod Echo(EchoRequest, EchoResponse);
     ESPmethod AddPackage(AddPackageRequest, AddPackageResponse);
+    ESPmethod CopyPackageMap(CopyPackageMapRequest, CopyPackageMapResponse);
     ESPmethod DeletePackage(DeletePackageRequest, DeletePackageResponse);
     ESPmethod ActivatePackage(ActivatePackageRequest, ActivatePackageResponse);
     ESPmethod DeActivatePackage(DeActivatePackageRequest, DeActivatePackageResponse);

+ 1 - 0
esp/services/ws_packageprocess/CMakeLists.txt

@@ -45,6 +45,7 @@ include_directories (
          ${HPCC_SOURCE_DIR}/common/remote
          ${HPCC_SOURCE_DIR}/common/workunit
          ${HPCC_SOURCE_DIR}/rtl/include
+         ${HPCC_SOURCE_DIR}/esp/smc/SMCLib
     )
 
 ADD_DEFINITIONS( -D_USRDLL )

+ 183 - 20
esp/services/ws_packageprocess/ws_packageprocessService.cpp

@@ -27,6 +27,7 @@
 #include "packageprocess_errors.h"
 #include "referencedfilelist.hpp"
 #include "package.h"
+#include "eclwatch_errorlist.hpp"
 
 #define SDS_LOCK_TIMEOUT (5*60*1000) // 5mins, 30s a bit short
 
@@ -182,11 +183,10 @@ void makePackageActive(IPropertyTree *pkgSet, IPropertyTree *psEntryNew, const c
         psEntryNew->setPropBool("@active", activate);
 }
 
-void fixPackageMapFileIds(IPropertyTree *pm, bool preloadAll)
+void fixPackageMapFileIds(IPropertyTreeIterator *iter, bool preloadAll)
 {
-    if (!pm)
+    if (!iter)
         return;
-    Owned<IPropertyTreeIterator> iter = pm->getElements("Package");
     ForEach(*iter)
     {
         IPropertyTree &item = iter->query();
@@ -217,6 +217,17 @@ void fixPackageMapFileIds(IPropertyTree *pm, bool preloadAll)
     }
 }
 
+void fixPackageMapFileIds(IPropertyTree *pm, bool preloadAll)
+{
+    if (!pm)
+        return;
+    Owned<IPropertyTreeIterator> iter = pm->getElements("Package");
+    fixPackageMapFileIds(iter, preloadAll);
+
+    iter.setown(pm->getElements("Part/Package"));
+    fixPackageMapFileIds(iter, preloadAll);
+}
+
 //////////////////////////////////////////////////////////
 
 #define PKGADD_DFS_OVERWRITE    0x0001
@@ -287,13 +298,17 @@ public:
         process.set(name);
         buildPkgSetId(pkgSetId, process);
     }
-    void setUser(const char *user, const char *password)
+    void setUser(const char *user, const char *password, IEspContext *context)
     {
-        if (user && *user && *password && *password)
+        if (user && *user && password && *password)
         {
             userdesc.setown(createUserDescriptor());
             userdesc->set(user, password);
+            return;
         }
+        if (!context)
+            return;
+        setUser(context->queryUserId(), context->queryPassword(), nullptr);
     }
     void setDerivedDfsLocation(const char *dfsLocation, const char *srcProcess)
     {
@@ -328,14 +343,14 @@ public:
         if (pmExisting && !pmExisting->getPropBool("@multipart", false))
             convertExisting();
     }
-    void createPart(const char *partname, const char *xml)
+    void createPart(const char *partname, IPropertyTree *pTree)
     {
         if (!partname || !*partname)
             throw MakeStringExceptionDirect(PKG_INFO_NOT_DEFINED, "No PackageMap Part name provided");
-        if (!xml || !*xml)
+        if (!pTree)
             throw MakeStringExceptionDirect(PKG_INFO_NOT_DEFINED, "No PackageMap content provided");
 
-        pmPart.setown(createPTreeFromXMLString(xml));
+        pmPart.set(pTree);
         if (!pmPart)
             throw MakeStringExceptionDirect(PKG_INFO_NOT_DEFINED, "Invalid PackageMap content");
         pmPart->addProp("@id", partname);
@@ -345,29 +360,47 @@ public:
 
         fixPackageMapFileIds(pmPart, checkFlag(PKGADD_PRELOAD_ALL));
     }
+    void cloneDfsInfo(unsigned updateFlags, StringArray &filesNotFound, IPropertyTree *pt)
+    {
+        cloneFileInfoToDali(updateFlags, filesNotFound, pt, daliIP, ensureClusterInfo(), srcCluster, prefix, userdesc, checkFlag(PKGADD_ALLOW_FOREIGN));
+    }
     void cloneDfsInfo(unsigned updateFlags, StringArray &filesNotFound)
     {
-        cloneFileInfoToDali(updateFlags, filesNotFound, pmPart, daliIP, ensureClusterInfo(), srcCluster, prefix, userdesc, checkFlag(PKGADD_ALLOW_FOREIGN));
+        cloneDfsInfo(updateFlags, filesNotFound, pmPart);
     }
-    void doCreate(const char *partname, const char *xml, unsigned updateFlags, StringArray &filesNotFound)
+    void doCreate(const char *partname, IPropertyTree *pTree, unsigned updateFlags, StringArray &filesNotFound)
     {
-        createPart(partname, xml);
-
+        if (!pTree)
+            throw MakeStringExceptionDirect(PKG_INFO_NOT_DEFINED, "No PackageMap content provided");
+        Linked<IPropertyTree> pmTree = pTree;
         if (pmExisting)
         {
             if (!checkFlag(PKGADD_MAP_REPLACE))
                 throw MakeStringException(PKG_NAME_EXISTS, "PackageMap %s already exists, either delete it or specify overwrite", pmid.str());
         }
 
-        cloneDfsInfo(updateFlags, filesNotFound);
+        if (pmTree->hasProp("Part"))
+        {
+            fixPackageMapFileIds(pmTree, checkFlag(PKGADD_PRELOAD_ALL));
+            cloneDfsInfo(updateFlags, filesNotFound, pmTree);
+        }
+        else
+        {
+            createPart(partname, pmTree.getClear()); //this is a part, not a whole packagemap
+            cloneDfsInfo(updateFlags, filesNotFound, pmPart);
+        }
 
         if (pmExisting)
             packageMaps->removeTree(pmExisting);
 
-        Owned<IPropertyTree> pmTree = createPTree("PackageMap", ipt_ordered);
+        if (!pmTree)
+        {
+            //wrap part in full PackageMap
+            pmTree.setown(createPTree("PackageMap", ipt_ordered));
+            pmTree->setPropBool("@multipart", true);
+            pmTree->addPropTree("Part", pmPart.getClear());
+        }
         pmTree->setProp("@id", pmid);
-        pmTree->setPropBool("@multipart", true);
-        pmTree->addPropTree("Part", pmPart.getClear());
         packageMaps->addPropTree("PackageMap", pmTree.getClear());
 
         VStringBuffer xpath("PackageMap[@id='%s'][@querySet='%s']", pmid.str(), target.get());
@@ -382,11 +415,49 @@ public:
         }
         makePackageActive(pkgSet, psEntry, target, checkFlag(PKGADD_MAP_ACTIVATE));
     }
+    void doCreate(const char *partname, const char *xml, unsigned updateFlags, StringArray &filesNotFound)
+    {
+        Owned<IPropertyTree> pTree = createPTreeFromXMLString(xml, ipt_ordered);
+        doCreate(partname, pTree, updateFlags, filesNotFound);
+    }
     void create(const char *partname, const char *xml, unsigned updateFlags, StringArray &filesNotFound)
     {
         init();
         doCreate(partname, xml, updateFlags, filesNotFound);
     }
+    void copy(IPropertyTree *pm, const char *name, unsigned updateFlags, StringArray &filesNotFound)
+    {
+        init();
+        doCreate(name, pm, updateFlags, filesNotFound);
+    }
+    void copy(const char *srcAddress, const char *srcTarget, const char *name, unsigned updateFlags, StringArray &filesNotFound)
+    {
+        VStringBuffer url("http://%s/WsPackageProcess", (srcAddress && *srcAddress) ? srcAddress : ".:8010");
+        Owned<IClientWsPackageProcess> client = createWsPackageProcessClient();
+        StringBuffer user, pw;
+        if (userdesc)
+            client->setUsernameToken(userdesc->getUserName(user), userdesc->getPassword(pw), nullptr);
+        client->addServiceUrl(url);
+
+        StringBuffer scopedPMID;
+        if (srcTarget && *srcTarget)
+            scopedPMID.append(srcTarget).append("::");
+        scopedPMID.append(name);
+        Owned<IClientGetPackageMapByIdRequest> req = client->createGetPackageMapByIdRequest();
+        req->rpc().setConnectTimeOutMs(HTTP_CLIENT_DEFAULT_CONNECT_TIMEOUT); //could make configurable, and consider changing read timeout,
+                                                                             //but do so across all copy type methods under a stand alone JIRA
+
+        req->setPackageMapId(scopedPMID);
+        Owned<IClientGetPackageMapByIdResponse> resp = client->GetPackageMapById(req);
+        if (resp->getExceptions().ordinality())
+        {
+            Owned<IMultiException> mE = makeMultiException("ESP");
+            mE->append(const_cast<IMultiException&>(resp->getExceptions()));
+            throw mE.getClear();
+        }
+        init();
+        doCreate(name, resp->getInfo(), updateFlags, filesNotFound);
+    }
     void addPart(const char *partname, const char *xml, unsigned updateFlags, StringArray &filesNotFound)
     {
         init();
@@ -397,7 +468,8 @@ public:
             return;
         }
 
-        createPart(partname, xml);
+        Owned<IPropertyTree> pTree = createPTreeFromXMLString(xml, ipt_ordered);
+        createPart(partname, pTree.getClear());
 
         VStringBuffer xpath("Part[@id='%s']", partname);
         IPropertyTree *existingPart = pmExisting->queryPropTree(xpath);
@@ -681,11 +753,15 @@ bool CWsPackageProcessEx::readPackageMapString(const char *packageMapString, Str
     return true;
 }
 
-void CWsPackageProcessEx::getPkgInfoById(const char *packageMapId, IPropertyTree* tree)
+void CWsPackageProcessEx::getPkgInfoById(const char *target, const char *packageMapId, IPropertyTree* tree)
 {
     if (!packageMapId || !*packageMapId)
         return;
 
+    StringBuffer scopedPMID;
+    if (target && *target)
+        packageMapId = scopedPMID.append(target).append("::").append(packageMapId).str();
+
     Owned<IPropertyTree> packageMaps = packageMapAndSet.getPackageMaps();
     if (!packageMaps)
         throw MakeStringException(PKG_DALI_LOOKUP_ERROR, "Unable to retrieve information about package maps from dali server");
@@ -700,6 +776,11 @@ void CWsPackageProcessEx::getPkgInfoById(const char *packageMapId, IPropertyTree
         mergePTree(tree, mapTree);
 }
 
+void CWsPackageProcessEx::getPkgInfoById(const char *packageMapId, IPropertyTree* tree)
+{
+    getPkgInfoById(nullptr, packageMapId, tree);
+}
+
 bool CWsPackageProcessEx::onAddPackage(IEspContext &context, IEspAddPackageRequest &req, IEspAddPackageResponse &resp)
 {
     PackageMapUpdater updater;
@@ -711,7 +792,7 @@ bool CWsPackageProcessEx::onAddPackage(IEspContext &context, IEspAddPackageReque
 
     updater.setPMID(req.getTarget(), req.getPackageMap(), req.getGlobalScope());
     updater.setProcess(req.getProcess());
-    updater.setUser(context.queryUserId(), context.queryPassword());
+    updater.setUser(context.queryUserId(), context.queryPassword(), nullptr);
     updater.setDerivedDfsLocation(req.getDaliIp(), req.getSourceProcess());
 
     unsigned updateFlags = 0;
@@ -735,6 +816,88 @@ bool CWsPackageProcessEx::onAddPackage(IEspContext &context, IEspAddPackageReque
     return true;
 }
 
+#define PACKAGEMAP_PATH_SEP_CHAR '/'
+
+bool nextPmPathNode(const char *&path, StringBuffer &node)
+{
+    if (*path==PACKAGEMAP_PATH_SEP_CHAR)
+        path++;
+    while (*path && *path!=PACKAGEMAP_PATH_SEP_CHAR)
+        node.append(*path++);
+    return (*path && *++path);
+}
+
+bool splitPMPath(const char *path, StringBuffer &netAddress, StringBuffer &target, StringBuffer *pmid)
+{
+    if (!path || !*path)
+        return false;
+    if (*path==PACKAGEMAP_PATH_SEP_CHAR && path[1]==PACKAGEMAP_PATH_SEP_CHAR)
+    {
+        path+=2;
+        if (!nextPmPathNode(path, netAddress))
+            return false;
+    }
+    if (!nextPmPathNode(path, target))
+        return (pmid==NULL);
+    if (!pmid)
+        return false;
+    if (nextPmPathNode(path, *pmid))
+        return false; //query path too deep
+    return true;
+}
+
+
+bool CWsPackageProcessEx::onCopyPackageMap(IEspContext &context, IEspCopyPackageMapRequest &req, IEspCopyPackageMapResponse &resp)
+{
+    PackageMapUpdater updater;
+    updater.setFlag(PKGADD_MAP_CREATE);
+    updater.setFlag(PKGADD_MAP_ACTIVATE, req.getActivate());
+    updater.setFlag(PKGADD_MAP_REPLACE, req.getReplacePackageMap());
+    updater.setFlag(PKGADD_PRELOAD_ALL, req.getPreloadAllPackages());
+    updater.setFlag(PKGADD_ALLOW_FOREIGN, true);
+
+    updater.setProcess(req.getProcess());
+    updater.setUser(context.queryUserId(), context.queryPassword(), &context);
+    updater.setDerivedDfsLocation(req.getDaliIp(), req.getSourceProcess());
+
+    unsigned updateFlags = 0;
+    if (req.getReplacePackageMap())
+        updateFlags |= DALI_UPDATEF_PACKAGEMAP;
+    if (req.getUpdateCloneFrom())
+        updateFlags |= DALI_UPDATEF_CLONE_FROM;
+    if (req.getUpdateSuperFiles())
+        updateFlags |= DALI_UPDATEF_SUPERFILES;
+    if (req.getAppendCluster())
+        updateFlags |= DALI_UPDATEF_APPEND_CLUSTER;
+
+    StringBuffer srcAddress, srcTarget, srcPMID;
+    if (!splitPMPath(req.getSourcePath(), srcAddress, srcTarget, &srcPMID))
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid source PackageMap path");
+
+    const char *requestedPMID = req.getPMID();
+    if (requestedPMID && *requestedPMID)
+        updater.setPMID(req.getTarget(), requestedPMID, false);
+    else
+        updater.setPMID(req.getTarget(), srcPMID, false);
+
+    StringArray filesNotFound;
+    if (srcAddress && *srcAddress)
+        updater.copy(srcAddress, srcTarget, srcPMID, updateFlags, filesNotFound);
+    else
+    {
+        Owned<IPropertyTree> tree = createPTree("PackageMaps");
+        getPkgInfoById(srcTarget, srcPMID, tree);
+        if (!tree->hasChildren())
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Source PackageMap not found");
+        updater.copy(tree, srcPMID, updateFlags, filesNotFound);
+    }
+    resp.setFilesNotFound(filesNotFound);
+
+    resp.updateStatus().setCode(0);
+    resp.updateStatus().setDescription(StringBuffer("Successfully loaded ").append(srcPMID.str()));
+    return true;
+}
+
 void CWsPackageProcessEx::deletePackage(const char *packageMap, const char *target, const char *process, bool globalScope, StringBuffer &returnMsg, int &returnCode)
 {
     bool ret = deletePkgInfo(packageMap, target, process, globalScope);
@@ -1187,7 +1350,7 @@ bool CWsPackageProcessEx::onAddPartToPackageMap(IEspContext &context, IEspAddPar
 
     updater.setPMID(req.getTarget(), req.getPackageMap(), req.getGlobalScope());
     updater.setProcess(req.getProcess());
-    updater.setUser(context.queryUserId(), context.queryPassword());
+    updater.setUser(context.queryUserId(), context.queryPassword(), nullptr);
     updater.setDerivedDfsLocation(req.getDaliIp(), req.getSourceProcess());
 
     unsigned updateFlags = 0;

+ 2 - 0
esp/services/ws_packageprocess/ws_packageprocessService.hpp

@@ -136,6 +136,7 @@ public:
 class CWsPackageProcessEx : public CWsPackageProcess
 {
     bool readPackageMapString(const char *packageMapString, StringBuffer &target, StringBuffer &process, StringBuffer &packageMap);
+    void getPkgInfoById(const char *target, const char *packageMapId, IPropertyTree* tree);
     void getPkgInfoById(const char *packageMapId, IPropertyTree* tree);
     void deletePackage(const char *packageMap, const char *target, const char *process, bool globalScope, StringBuffer &returnMsg, int &returnCode);
 public:
@@ -149,6 +150,7 @@ public:
 
     virtual bool onEcho(IEspContext &context, IEspEchoRequest &req, IEspEchoResponse &resp);
     virtual bool onAddPackage(IEspContext &context, IEspAddPackageRequest &req, IEspAddPackageResponse &resp);
+    virtual bool onCopyPackageMap(IEspContext &context, IEspCopyPackageMapRequest &req, IEspCopyPackageMapResponse &resp);
     virtual bool onDeletePackage(IEspContext &context, IEspDeletePackageRequest &req, IEspDeletePackageResponse &resp);
     virtual bool onActivatePackage(IEspContext &context, IEspActivatePackageRequest &req, IEspActivatePackageResponse &resp);
     virtual bool onDeActivatePackage(IEspContext &context, IEspDeActivatePackageRequest &req, IEspDeActivatePackageResponse &resp);