Repository: dmwm/wmcore
Issue: DBS3Upload fails to insert some StepChain data into DBS

As reported in this JIRA ticket: https://its.cern.ch/jira/browse/CMSCOMPPR-6517

vocms0257 is failing to inject the output blocks for this workflow:
cmsunified_task_L1T-PhaseIITDRSpring19GS-00017__v1_T_190605_130259_6694

with this exception in the logs [1]. 

As discussed in the JIRA with Yuyi, the data posted to the blockDump API is not consistent, see:
http://amaltaro.web.cern.ch/amaltaro/forYuyi/dbsuploader_block.json

where
```
dataset
    "processed_ds_name": "PhaseIITDRSpring19DR-PU200_106X_upgrade2023_realistic_v3-v2", 
acquisition_era
    "acquisition_era_name": "PhaseIITDRSpring19GS", 
```

We need to investigate how this information is gathered at the DBS3Upload.

[1]
```
2019-06-13 11:21:10,068:140417954010880:ERROR:DBSUploadPoller:Error trying to process block /Mu_FlatPt2to100-pythia8-gun/PhaseIITDRSpring19DR-PU200_106X_upgrade2023_realistic_v3-v2/GEN-SIM-DIGI-RAW#9dbb8c96-d5d4-4f01-9682-6caf56699596 through DBS. Error: HTTP Error 400: BlockInsert:                   processed_ds_name=acquisition_era_name[-filter_name][-processing_str]-vprocessing_version must be satisified.
Traceback (most recent call last):
  File "/data/srv/wmagent/v1.2.2.patch2/sw/slc7_amd64_gcc630/cms/wmagent/1.2.2.patch2/lib/python2.7/site-packages/WMComponent/DBS3Buffer/DBSUploadPoller.py", line 112, in uploadWorker
    dbsApi.insertBulkBlock(blockDump=block)
  File "/data/srv/wmagent/v1.2.2.patch2/sw/slc7_amd64_gcc630/cms/dbs3-client/3.7.8/lib/python2.7/site-packages/dbs/apis/dbsClient.py", line 342, in insertBulkBlock
    result =  self.__callServer("bulkblocks", data=blockDump, callmethod='POST' )
  File "/data/srv/wmagent/v1.2.2.patch2/sw/slc7_amd64_gcc630/cms/dbs3-client/3.7.8/lib/python2.7/site-packages/dbs/apis/dbsClient.py", line 199, in __callServer
    self.__parseForException(http_error)
  File "/data/srv/wmagent/v1.2.2.patch2/sw/slc7_amd64_gcc630/cms/dbs3-client/3.7.8/lib/python2.7/site-packages/dbs/apis/dbsClient.py", line 226, in __parseForException
    raise HTTPError(http_error.url, data['exception'], data['message'], http_error.header, http_error.body)
HTTPError: HTTP Error 400: BlockInsert:                   processed_ds_name=acquisition_era_name[-filter_name][-processing_str]-vprocessing_version must be satisified.
```

Changed files:
src/python/WMCore/WMSpec/WMTask.py, test/python/WMCore_t/WMSpec_t/Steps_t/Templates_t/CMSSWTemplate_t.py, src/python/WMCore/WMSpec/StdSpecs/StdBase.py, src/python/WMCore/WMSpec/Steps/Executors/CMSSW.py, test/python/WMCore_t/WMSpec_t/WMWorkload_t.py, src/python/WMCore/WMSpec/Steps/Templates/CMSSW.py, src/python/WMCore/WMSpec/StdSpecs/StepChain.py, src/python/WMCore/WMSpec/WMWorkload.py

diff --git a/src/python/WMCore/WMSpec/StdSpecs/StdBase.py b/src/python/WMCore/WMSpec/StdSpecs/StdBase.py
index 2090763b2..0c9199b7b 100644
--- a/src/python/WMCore/WMSpec/StdSpecs/StdBase.py
+++ b/src/python/WMCore/WMSpec/StdSpecs/StdBase.py
@@ -392,6 +392,7 @@ class StdBase(object):
         acqEra = taskConf.get("AcquisitionEra") or self.acquisitionEra
         procStr = taskConf.get("ProcessingString") or self.processingString
         procVer = taskConf.get("ProcessingVersion") or self.processingVersion
+        prepID = taskConf.get("PrepID") or self.prepID
         procTask.setAcquisitionEra(acqEra)
         procTask.setProcessingString(procStr)
         procTask.setProcessingVersion(procVer)
@@ -431,6 +432,7 @@ class StdBase(object):
         procTaskCmsswHelper.setUserSandbox(userSandbox)
         procTaskCmsswHelper.setUserFiles(userFiles)
         procTaskCmsswHelper.setGlobalTag(globalTag)
+        procTaskCmsswHelper.setPrepId(prepID)
         procTaskCmsswHelper.setOverrideCatalog(self.overrideCatalog)
         procTaskCmsswHelper.setErrorDestinationStep(stepName=procTaskLogArch.name())
 
@@ -479,7 +481,6 @@ class StdBase(object):
             procTaskCmsswHelper.setDataProcessingConfig(scenarioName, scenarioFunc,
                                                         **scenarioArgs)
         # only in the very end, in order to get it in for the children tasks as well
-        prepID = taskConf.get("PrepID") or self.prepID
         procTask.setPrepID(prepID)
 
         # has to be done in the very end such that child tasks are set too
diff --git a/src/python/WMCore/WMSpec/StdSpecs/StepChain.py b/src/python/WMCore/WMSpec/StdSpecs/StepChain.py
index 9417c224c..393bc00e3 100644
--- a/src/python/WMCore/WMSpec/StdSpecs/StepChain.py
+++ b/src/python/WMCore/WMSpec/StdSpecs/StepChain.py
@@ -298,6 +298,7 @@ class StepChainWorkloadFactory(StdBase):
             globalTag = self.getStepValue('GlobalTag', taskConf, self.globalTag)
             frameworkVersion = self.getStepValue('CMSSWVersion', taskConf, self.frameworkVersion)
             scramArch = self.getStepValue('ScramArch', taskConf, self.scramArch)
+            prepId = self.getStepValue('PrepID', taskConf, self.prepID)
 
             currentCmssw = parentCmsswStep.addTopStep(currentCmsRun)
             currentCmssw.setStepType("CMSSW")
@@ -305,6 +306,7 @@ class StepChainWorkloadFactory(StdBase):
             template(currentCmssw.data)
 
             currentCmsswStepHelper = currentCmssw.getTypeHelper()
+            currentCmsswStepHelper.setPrepId(prepId)
             currentCmsswStepHelper.setGlobalTag(globalTag)
             currentCmsswStepHelper.setupChainedProcessing(parentCmsRun, taskConf['InputFromOutputModule'])
             currentCmsswStepHelper.cmsswSetup(frameworkVersion, softwareEnvironment="", scramArch=scramArch)
diff --git a/src/python/WMCore/WMSpec/Steps/Executors/CMSSW.py b/src/python/WMCore/WMSpec/Steps/Executors/CMSSW.py
index b535c6225..f8b6ebe50 100644
--- a/src/python/WMCore/WMSpec/Steps/Executors/CMSSW.py
+++ b/src/python/WMCore/WMSpec/Steps/Executors/CMSSW.py
@@ -276,13 +276,13 @@ class CMSSW(Executor):
         stepHelper = WMStepHelper(self.step)
         typeHelper = stepHelper.getTypeHelper()
 
-        acquisitionEra = self.task.getAcquisitionEra()
-        processingVer = self.task.getProcessingVersion()
-        processingStr = self.task.getProcessingString()
+        acquisitionEra = typeHelper.getAcqEra() or self.task.getAcquisitionEra()
+        processingVer = typeHelper.getProcVer() or self.task.getProcessingVersion()
+        processingStr = typeHelper.getProcStr() or self.task.getProcessingString()
+        prepID = typeHelper.getPrepId() or self.task.getPrepID()
+        globalTag = typeHelper.getGlobalTag()
         validStatus = self.workload.getValidStatus()
         inputPath = self.task.getInputDatasetPath()
-        globalTag = typeHelper.getGlobalTag()
-        prepID = self.task.getPrepID()
         campaign = self.workload.getCampaign()
         cacheUrl, cacheDB, configID = stepHelper.getConfigInfo()
 
diff --git a/src/python/WMCore/WMSpec/Steps/Templates/CMSSW.py b/src/python/WMCore/WMSpec/Steps/Templates/CMSSW.py
index a2e2f49a4..40107f17e 100644
--- a/src/python/WMCore/WMSpec/Steps/Templates/CMSSW.py
+++ b/src/python/WMCore/WMSpec/Steps/Templates/CMSSW.py
@@ -20,6 +20,61 @@ class CMSSWStepHelper(CoreHelper):
     for CMSSW tasks
 
     """
+    def setAcqEra(self, acqEra):
+        """
+        _setAcqEra_
+        Set the acquisition era attribute for this step.
+        """
+        self.data.output.acqEra = acqEra
+
+    def setProcStr(self, procStr):
+        """
+        _setProcStr_
+        Set the processing string attribute for this step.
+        """
+        self.data.output.procStr = procStr
+
+    def setProcVer(self, procVer):
+        """
+        _setProcVer_
+        Set the processing version era attribute for this step.
+        """
+        self.data.output.procVer = procVer
+
+    def getAcqEra(self):
+        """
+        _getAcqEra_
+        Retrieve the acquisition era for this step, or return None if non-existent.
+        """
+        return getattr(self.data.output, 'acqEra', None)
+
+    def getProcStr(self):
+        """
+        _getProcStr_
+        Retrieve the processing string for this step, or return None if non-existent.
+        """
+        return getattr(self.data.output, 'procStr', None)
+
+    def getProcVer(self):
+        """
+        _getProcVer_
+        Retrieve the processing version for this step, or return None if non-existent.
+        """
+        return getattr(self.data.output, 'procVer', None)
+
+    def setPrepId(self, prepId):
+        """
+        _setPrepId_
+        Set the prep_id attribute for this step.
+        """
+        self.data.output.prepId = prepId
+
+    def getPrepId(self):
+        """
+        _getPrepId_
+        Retrieve the prep_id for this step, or return None if non-existent.
+        """
+        return getattr(self.data.output, 'prepId', None)
 
     def addOutputModule(self, moduleName, **details):
         """
@@ -40,23 +95,6 @@ class CMSSWStepHelper(CoreHelper):
 
         return
 
-    def addAnalysisFile(self, fileLabel, **details):
-        """
-        _addAnalysisFile_
-
-        Add in an additional file produced by the user to be staged out
-        """
-        analysisFiles = self.data.output.analysisFiles
-
-        if getattr(analysisFiles, fileLabel, None) == None:
-            analysisFiles.section_(fileLabel)
-        analysisFile = getattr(analysisFiles, fileLabel)
-
-        for key, value in details.items():
-            setattr(analysisFile, key, value)
-
-        return
-
     def listOutputModules(self):
         """
         _listOutputModules_
diff --git a/src/python/WMCore/WMSpec/WMTask.py b/src/python/WMCore/WMSpec/WMTask.py
index b4a67f2d5..ae2f3742f 100644
--- a/src/python/WMCore/WMSpec/WMTask.py
+++ b/src/python/WMCore/WMSpec/WMTask.py
@@ -1306,14 +1306,15 @@ class WMTaskHelper(TreeHelper):
                 IDs.append(ID)
         return IDs
 
-    def setProcessingVersion(self, procVer, parentProcessingVersion=0, stepChain=False):
+    def setProcessingVersion(self, procVer, parentProcessingVersion=0, stepChainMap=False):
         """
         _setProcessingVersion_
 
         Set the task processing version
         """
-        if isinstance(procVer, dict) and stepChain:
+        if isinstance(procVer, dict) and stepChainMap:
             taskProcVer = self._getStepValue(procVer, parentProcessingVersion)
+            self._setStepProperty("ProcessingVersion", procVer, stepChainMap)
         elif isinstance(procVer, dict):
             taskProcVer = procVer.get(self.name(), parentProcessingVersion)
             if taskProcVer is None:
@@ -1325,7 +1326,7 @@ class WMTaskHelper(TreeHelper):
 
         self.data.parameters.processingVersion = int(taskProcVer)
         for task in self.childTaskIterator():
-            task.setProcessingVersion(procVer, taskProcVer, stepChain)
+            task.setProcessingVersion(procVer, taskProcVer, stepChainMap)
         return
 
     def getProcessingVersion(self):
@@ -1336,14 +1337,15 @@ class WMTaskHelper(TreeHelper):
         """
         return getattr(self.data.parameters, 'processingVersion', 0)
 
-    def setProcessingString(self, procString, parentProcessingString=None, stepChain=False):
+    def setProcessingString(self, procString, parentProcessingString=None, stepChainMap=False):
         """
         _setProcessingString_
 
         Set the task processing string
         """
-        if isinstance(procString, dict) and stepChain:
+        if isinstance(procString, dict) and stepChainMap:
             taskProcString = self._getStepValue(procString, parentProcessingString)
+            self._setStepProperty("ProcessingString", procString, stepChainMap)
         elif isinstance(procString, dict):
             taskProcString = procString.get(self.name(), parentProcessingString)
             if taskProcString is None:
@@ -1356,7 +1358,7 @@ class WMTaskHelper(TreeHelper):
         self.data.parameters.processingString = taskProcString
 
         for task in self.childTaskIterator():
-            task.setProcessingString(procString, taskProcString, stepChain)
+            task.setProcessingString(procString, taskProcString, stepChainMap)
         return
 
     def getProcessingString(self):
@@ -1446,15 +1448,40 @@ class WMTaskHelper(TreeHelper):
 
         return value
 
-    def setAcquisitionEra(self, era, parentAcquisitionEra=None, stepChain=False):
+    def _setStepProperty(self, propertyName, propertyDict, stepMap):
+        """
+        For StepChain workloads, we also need to set AcqEra/ProcStr/ProcVer
+        at the WMStep level, such that we can properly map different cmsRun
+        steps - within the same task - to different meta data information.
+        :param propertyName: the name of the property to set at step level
+        :param propertyDict: a dictionary mapping StepName to its value
+        :param stepMap: map between step name, step number and cmsRun number,
+                        same as returned from the workload getStepMapping
+        """
+        propMethodMap = {"AcquisitionEra": "setAcqEra",
+                         "ProcessingString": "setProcStr",
+                         "ProcessingVersion": "setProcStr"}
+
+        if self.taskType() not in ["Production", "Processing"]:
+            # then there is no need to set anything, single cmsRun step at most
+            return
+
+        for stepName, stepValues in stepMap.items():
+            cmsRunNum = stepValues[1]
+            stepHelper = self.getStepHelper(cmsRunNum)
+            callableMethod = getattr(stepHelper, propMethodMap[propertyName])
+            callableMethod(propertyDict[stepName])
+
+    def setAcquisitionEra(self, era, parentAcquisitionEra=None, stepChainMap=False):
         """
         _setAcquistionEra_
 
         Set the task acquisition era
         """
 
-        if isinstance(era, dict) and stepChain:
+        if isinstance(era, dict) and stepChainMap:
             taskEra = self._getStepValue(era, parentAcquisitionEra)
+            self._setStepProperty("AcquisitionEra", era, stepChainMap)
         elif isinstance(era, dict):
             taskEra = era.get(self.name(), parentAcquisitionEra)
             if taskEra is None:
@@ -1470,14 +1497,14 @@ class WMTaskHelper(TreeHelper):
         self.data.parameters.acquisitionEra = taskEra
 
         for task in self.childTaskIterator():
-            task.setAcquisitionEra(era, taskEra, stepChain)
+            task.setAcquisitionEra(era, taskEra, stepChainMap)
         return
 
     def getAcquisitionEra(self):
         """
         _getAcquisitionEra_
 
-        Get the task acquisition era
+        Get the task acquisition era.
         """
         return getattr(self.data.parameters, 'acquisitionEra', None)
 
diff --git a/src/python/WMCore/WMSpec/WMWorkload.py b/src/python/WMCore/WMSpec/WMWorkload.py
index 287f9fe26..c78d74eb0 100644
--- a/src/python/WMCore/WMSpec/WMWorkload.py
+++ b/src/python/WMCore/WMSpec/WMWorkload.py
@@ -104,14 +104,13 @@ class WMWorkloadHelper(PersistencyHelper):
         Used for properly setting AcqEra/ProcStr/ProcVer for each step in a StepChain request
         during assignment. Only used if one of those parameters is a dictionary.
         """
-        mustSet = False
         if "AcquisitionEra" in assignArgs and isinstance(assignArgs["AcquisitionEra"], dict):
-            mustSet = True
+            pass
         elif "ProcessingString" in assignArgs and isinstance(assignArgs["ProcessingString"], dict):
-            mustSet = True
+            pass
         elif "ProcessingVersion" in assignArgs and isinstance(assignArgs["ProcessingVersion"], dict):
-            mustSet = True
-        if mustSet is False:
+            pass
+        else:
             return
 
         stepNameMapping = self.getStepMapping()
@@ -886,7 +885,7 @@ class WMWorkloadHelper(PersistencyHelper):
         """
         stepNameMapping = self.getStepMapping()
         for task in self.taskIterator():
-            task.setAcquisitionEra(acquisitionEras, stepChain=stepNameMapping)
+            task.setAcquisitionEra(acquisitionEras, stepChainMap=stepNameMapping)
 
         self.updateLFNsAndDatasets()
         # set acquistionEra for workload (need to refactor)
@@ -903,7 +902,7 @@ class WMWorkloadHelper(PersistencyHelper):
         stepNameMapping = self.getStepMapping()
 
         for task in self.taskIterator():
-            task.setProcessingVersion(processingVersions, stepChain=stepNameMapping)
+            task.setProcessingVersion(processingVersions, stepChainMap=stepNameMapping)
 
         self.updateLFNsAndDatasets()
         self.data.properties.processingVersion = processingVersions
@@ -919,7 +918,7 @@ class WMWorkloadHelper(PersistencyHelper):
         stepNameMapping = self.getStepMapping()
 
         for task in self.taskIterator():
-            task.setProcessingString(processingStrings, stepChain=stepNameMapping)
+            task.setProcessingString(processingStrings, stepChainMap=stepNameMapping)
 
         self.updateLFNsAndDatasets()
         self.data.properties.processingString = processingStrings
diff --git a/test/python/WMCore_t/WMSpec_t/Steps_t/Templates_t/CMSSWTemplate_t.py b/test/python/WMCore_t/WMSpec_t/Steps_t/Templates_t/CMSSWTemplate_t.py
index 978452cb9..fa07b0156 100644
--- a/test/python/WMCore_t/WMSpec_t/Steps_t/Templates_t/CMSSWTemplate_t.py
+++ b/test/python/WMCore_t/WMSpec_t/Steps_t/Templates_t/CMSSWTemplate_t.py
@@ -11,6 +11,7 @@ import unittest
 
 from WMCore.WMSpec.Steps.Templates.CMSSW import CMSSW as CMSSWTemplate
 from WMCore.WMSpec.WMWorkload import newWorkload
+from WMCore.WMSpec.WMStep import makeWMStep
 
 
 class CMSSWTemplateTest(unittest.TestCase):
@@ -121,6 +122,39 @@ class CMSSWTemplateTest(unittest.TestCase):
         self.assertEqual(helper.data.input.inputStepName, "some_inputStepName")
         self.assertEqual(helper.data.input.inputOutputModule, "some_inputOutputModule")
 
+    def testFileProperties(self):
+        """
+        _testFileProperties_
+
+        Test some CMSSW step output file properties
+        """
+        step = makeWMStep("cmsRun1")
+        step.setStepType("CMSSW")
+        template = CMSSWTemplate()
+        template(step.data)
+        helper = step.getTypeHelper()
+
+        # default values
+        self.assertIsNone(helper.getAcqEra(), None)
+        self.assertIsNone(helper.getProcStr(), None)
+        self.assertIsNone(helper.getProcVer(), None)
+        self.assertIsNone(helper.getPrepId(), None)
+        self.assertEqual(helper.listOutputModules(), [])
+
+        # now write something to the step object
+        helper.setAcqEra("TestAcqEra")
+        helper.setProcStr("TestProcStr")
+        helper.setProcVer(111)
+        helper.setPrepId("TestPrepId")
+        helper.addOutputModule("Merged", primaryDataset="Primary",
+                               processedDataset="Processed", dataTier="RECO")
+
+        self.assertEqual(helper.getAcqEra(), "TestAcqEra")
+        self.assertEqual(helper.getProcStr(), "TestProcStr")
+        self.assertEqual(helper.getProcVer(), 111)
+        self.assertEqual(helper.getPrepId(), "TestPrepId")
+        self.assertItemsEqual(helper.listOutputModules(), ["Merged"])
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/python/WMCore_t/WMSpec_t/WMWorkload_t.py b/test/python/WMCore_t/WMSpec_t/WMWorkload_t.py
index 40d9bd550..557053d8e 100644
--- a/test/python/WMCore_t/WMSpec_t/WMWorkload_t.py
+++ b/test/python/WMCore_t/WMSpec_t/WMWorkload_t.py
@@ -9,11 +9,11 @@ import os
 import unittest
 
 import WMCore_t.WMSpec_t.TestWorkloads as TestSpecs
-
+from copy import copy
 from WMCore.WMSpec.WMSpecErrors import WMSpecFactoryException
 from WMCore.WMSpec.WMTask import WMTask, WMTaskHelper
 from WMCore.WMSpec.WMWorkload import WMWorkload, WMWorkloadHelper
-
+from WMCore.WMSpec.Steps.Templates.CMSSW import CMSSW as CMSSWTemplate
 
 class WMWorkloadTest(unittest.TestCase):
     def setUp(self):
@@ -139,6 +139,99 @@ class WMWorkloadTest(unittest.TestCase):
                 mergeTaskCMSSWHelper, skimTaskCMSSWHelper,
                 harvestTaskCMSSWHelper)
 
+    def makeStepChainTestWorkload(self):
+        """
+        _makeStepChainTestWorkload_
+
+        Make a StepChain workload with a single processing task and multiple
+        cmsRun steps within it.
+        Returns the workload and all the cmssw helpers
+        """
+        testWorkload = WMWorkloadHelper(WMWorkload("TestStepChainWorkload"))
+
+        procTask = testWorkload.newTask("Test_StepName1")
+        procTask.setTaskType("Processing")
+
+        procTaskCMSSW = procTask.makeStep("cmsRun1")
+        procTaskCMSSW.setStepType("CMSSW")
+        template = CMSSWTemplate()
+        template(procTaskCMSSW.data)
+        procTaskCMSSWHelper = procTaskCMSSW.getTypeHelper()
+
+        procTaskCMSSW2 = procTaskCMSSW.addStep("cmsRun2")
+        procTaskCMSSW2.setStepType("CMSSW")
+        template = CMSSWTemplate()
+        template(procTaskCMSSW2.data)
+        procTaskCMSSW2Helper = procTaskCMSSW2.getTypeHelper()
+
+        procTaskCMSSW3 = procTaskCMSSW2.addStep("cmsRun3")
+        procTaskCMSSW3.setStepType("CMSSW")
+        template = CMSSWTemplate()
+        template(procTaskCMSSW3.data)
+        procTaskCMSSW3Helper = procTaskCMSSW3.getTypeHelper()
+
+        procTask.applyTemplates()
+        procTaskCMSSW2Helper.keepOutput(False)
+
+        primaryDataset = "bogusPrimary"
+        procTaskCMSSWHelper.addOutputModule("OutputA",
+                                            primaryDataset=primaryDataset,
+                                            processedDataset="bogusProcessedA",
+                                            dataTier="GEN-SIM",
+                                            lfnBase="bogusUnmerged",
+                                            mergedLFNBase="bogusMerged",
+                                            transient=True,  # we only save the output of merge
+                                            filterName=None)
+        procTaskCMSSW2Helper.addOutputModule("OutputC",
+                                             primaryDataset=primaryDataset,
+                                             processedDataset="bogusProcessedC",
+                                             dataTier="GEN-SIM-DIGI-RAW",
+                                             lfnBase="bogusUnmerged",
+                                             mergedLFNBase="bogusMerged",
+                                             transient=True,  # we only save the output of merge
+                                             filterName=None)
+        procTaskCMSSW3Helper.addOutputModule("OutputD",
+                                             primaryDataset=primaryDataset,
+                                             processedDataset="bogusProcessedD",
+                                             dataTier="AODSIM",
+                                             lfnBase="bogusUnmerged",
+                                             mergedLFNBase="bogusMerged",
+                                             transient=True,  # we only save the output of merge
+                                             filterName=None)
+
+        mergeTask = procTask.addTask("Test_StepName1MergeGENSIMTask")
+        mergeTask.setTaskType("Merge")
+        mergeTaskCMSSW = mergeTask.makeStep("cmsRun1")
+        mergeTaskCMSSW.setStepType("CMSSW")
+        mergeTaskCMSSWHelper = mergeTaskCMSSW.getTypeHelper()
+        mergeTask.applyTemplates()
+        mergeTaskCMSSWHelper.addOutputModule("Merged",
+                                             primaryDataset=primaryDataset,
+                                             processedDataset="bogusProcessedA",
+                                             dataTier="GEN-SIM",
+                                             lfnBase="bogusMerged",
+                                             mergedLFNBase="bogusMerged",
+                                             filterName=None)
+
+        merge3Task = procTask.addTask("Test_StepName3MergeAODSIMTask")
+        merge3Task.setTaskType("Merge")
+        merge3TaskCMSSW = merge3Task.makeStep("cmsRun1")
+        merge3TaskCMSSW.setStepType("CMSSW")
+        merge3TaskCMSSWHelper = merge3TaskCMSSW.getTypeHelper()
+        merge3Task.applyTemplates()
+        merge3TaskCMSSWHelper.addOutputModule("Merged",
+                                             primaryDataset=primaryDataset,
+                                             processedDataset="bogusProcessedD",
+                                             dataTier="AODSIM",
+                                             lfnBase="bogusMerged",
+                                             mergedLFNBase="bogusMerged",
+                                             filterName=None)
+
+
+        return (testWorkload, procTaskCMSSWHelper, procTaskCMSSW2Helper, procTaskCMSSW3Helper,
+                mergeTaskCMSSWHelper, merge3TaskCMSSWHelper)
+
+
     def testInstantiation(self):
         """
         _testInstantiation_
@@ -668,6 +761,164 @@ class WMWorkloadTest(unittest.TestCase):
                         "Error: A dataset is missing")
         return
 
+    def testUpdatingLFNAndDatasetStepChain(self):
+        """
+        _testUpdatingLFNAndDatasetStepChain_
+
+        Test whether we can properly set acquisition era, processing string,
+        processing version and update the LFNs and datasets for a workload
+        with multiple cmsRun steps inside the same processing task (read StepChain).
+        """
+        stepMap = {'Test_StepName1': ('Step1', 'cmsRun1'),
+                   'Test_StepName2': ('Step2', 'cmsRun2'),
+                   'Test_StepName3': ('Step3', 'cmsRun3')}
+
+        primaryDataset = "bogusPrimary"
+        acqEra = {"Test_StepName1": "AcqEra_StepRun1",
+                  "Test_StepName2": "AcqEra_StepRun2",
+                  "Test_StepName3": "AcqEra_StepRun3"}
+        procStr = {"Test_StepName1": "ProcStr_StepRun1",
+                   "Test_StepName2": "ProcStr_StepRun2",
+                   "Test_StepName3": "ProcStr_StepRun3"}
+        procVer = 2
+
+        outDsets = ["/%s/AcqEra_StepRun1-ProcStr_StepRun1-v2/GEN-SIM" % primaryDataset,
+                    "/%s/AcqEra_StepRun3-ProcStr_StepRun3-v2/AODSIM" % primaryDataset]
+
+        (testWorkload, procTaskCMSSWHelper, procTaskCMSSW2Helper, procTaskCMSSW3Helper,
+         mergeTaskCMSSWHelper, merge3TaskCMSSWHelper) = self.makeStepChainTestWorkload()
+
+        mergedLFNBase = "/store/data"
+        unmergedLFNBase = "/store/unmerged"
+        self.assertEqual(testWorkload.getLFNBases(), (mergedLFNBase, unmergedLFNBase))
+
+        # check default task values
+        for taskName in testWorkload.listAllTaskNames():
+            task = testWorkload.getTaskByName(taskName)
+            self.assertEqual(task.getAcquisitionEra(), None)
+            self.assertEqual(task.getProcessingString(), None)
+            self.assertEqual(task.getProcessingVersion(), 0)
+
+        # check default step values
+        for stepObj in (procTaskCMSSWHelper, procTaskCMSSW2Helper, procTaskCMSSW3Helper,
+                        mergeTaskCMSSWHelper, merge3TaskCMSSWHelper):
+            self.assertEqual(stepObj.getAcqEra(), None)
+            self.assertEqual(stepObj.getProcStr(), None)
+            self.assertEqual(stepObj.getProcVer(), None)
+
+        # NOW WRITE SOME STUFF TO THE WORKLOAD
+        testWorkload.setStepMapping(stepMap)
+        testWorkload.setAcquisitionEra(acqEra)
+        testWorkload.setProcessingString(procStr)
+        testWorkload.setProcessingVersion(procVer)
+        ### FIXME there has to be a better way to update it
+        kwargs = {}
+        kwargs.update({"AcquisitionEra": copy(acqEra)})
+        kwargs.update({"ProcessingString": copy(procStr)})
+        testWorkload.setStepProperties(kwargs)
+
+        # workload level checks
+        self.assertItemsEqual(testWorkload.getAcquisitionEra(), acqEra)
+        self.assertItemsEqual(testWorkload.getProcessingString(), procStr)
+        self.assertEqual(testWorkload.getProcessingVersion(), 2)
+        outputDatasets = testWorkload.listOutputDatasets()
+        self.assertItemsEqual(outputDatasets, outDsets)
+
+        # task level checks
+        for taskName in testWorkload.listAllTaskNames():
+            lookupKey = taskName.split("Merge")[0]
+            task = testWorkload.getTaskByName(taskName)
+            self.assertEqual(task.getAcquisitionEra(), acqEra[lookupKey])
+            self.assertEqual(task.getProcessingString(), procStr[lookupKey])
+            self.assertEqual(task.getProcessingVersion(), 2)
+
+        # step level checks
+        for stepObj in (procTaskCMSSWHelper, procTaskCMSSW2Helper, procTaskCMSSW3Helper):
+            taskName = [k for k, values in stepMap.items() if values[1] == stepObj.name()][0]
+            self.assertEqual(stepObj.getAcqEra(), acqEra[taskName])
+            self.assertEqual(stepObj.getProcStr(), procStr[taskName])
+            # FIXME: maybe we don't set ProcVer at step level
+            #self.assertEqual(stepObj.getProcVer(), procVer)
+
+        # no need to set those attributes for merge tasks/steps
+        for stepObj in (mergeTaskCMSSWHelper, merge3TaskCMSSWHelper):
+            self.assertEqual(stepObj.getAcqEra(), None)
+            self.assertEqual(stepObj.getProcStr(), None)
+
+        # now check the output information
+        outputModules = [procTaskCMSSWHelper.getOutputModule("OutputA"),
+                         procTaskCMSSW2Helper.getOutputModule("OutputC"),
+                         procTaskCMSSW3Helper.getOutputModule("OutputD"),
+                         mergeTaskCMSSWHelper.getOutputModule("Merged"),
+                         merge3TaskCMSSWHelper.getOutputModule("Merged")]
+
+        listAcqEra = ["AcqEra_StepRun1", "AcqEra_StepRun2", "AcqEra_StepRun3",
+                      "AcqEra_StepRun1", "AcqEra_StepRun3"]
+        listProcStr = ["ProcStr_StepRun1", "ProcStr_StepRun2", "ProcStr_StepRun3",
+                       "ProcStr_StepRun1", "ProcStr_StepRun3"]
+        for idx, outputModule in enumerate(outputModules):
+            self.assertEqual(outputModule.primaryDataset, "bogusPrimary")
+            acqEra = listAcqEra[idx]
+            procStr = listProcStr[idx]
+            dataTier = outputModule.dataTier
+            filterName = outputModule.filterName
+
+            self.assertIsNone(filterName)
+            procDataset = "%s-%s-v%s" % (acqEra, procStr, procVer)
+            self.assertEqual(outputModule.processedDataset, procDataset)
+
+            mergedLFN = "%s/%s/%s/%s/%s-v%s" % (mergedLFNBase, acqEra, primaryDataset, dataTier, procStr, procVer)
+            unmergedLFN = "%s/%s/%s/%s/%s-v%s" % (unmergedLFNBase, acqEra, primaryDataset, dataTier, procStr, procVer)
+
+            if outputModule._internal_name == "Merged":
+                self.assertEqual(outputModule.lfnBase, mergedLFN,
+                                 "Error: Incorrect unmerged LFN %s." % outputModule.lfnBase)
+                self.assertEqual(outputModule.mergedLFNBase, mergedLFN,
+                                 "Error: Incorrect merged LFN %s." % outputModule.mergedLFNBase)
+            else:
+                self.assertEqual(outputModule.lfnBase, unmergedLFN,
+                                 "Error: Incorrect unmerged LFN %s." % outputModule.lfnBase)
+                self.assertEqual(outputModule.mergedLFNBase, mergedLFN,
+                                 "Error: Incorrect merged LFN %s." % outputModule.mergedLFNBase)
+
+
+        mergedLFNBase = "/store/mc"
+        unmergedLFNBase = "/store/unmerged"
+        testWorkload.setLFNBase(mergedLFNBase, unmergedLFNBase)
+        # after updating anything related to the output, we need to call the below method
+        testWorkload.setStepProperties(kwargs)
+
+        self.assertEqual(testWorkload.getLFNBases(), (mergedLFNBase, unmergedLFNBase))
+
+        for idx, outputModule in enumerate(outputModules):
+            self.assertEqual(outputModule.primaryDataset, "bogusPrimary")
+            acqEra = listAcqEra[idx]
+            procStr = listProcStr[idx]
+            dataTier = outputModule.dataTier
+            filterName = outputModule.filterName
+
+            self.assertIsNone(filterName)
+            procDataset = "%s-%s-v%s" % (acqEra, procStr, procVer)
+            self.assertEqual(outputModule.processedDataset, procDataset)
+
+            mergedLFN = "%s/%s/%s/%s/%s-v%s" % (mergedLFNBase, acqEra, primaryDataset, dataTier, procStr, procVer)
+            unmergedLFN = "%s/%s/%s/%s/%s-v%s" % (unmergedLFNBase, acqEra, primaryDataset, dataTier, procStr, procVer)
+
+            if outputModule._internal_name == "Merged":
+                self.assertEqual(outputModule.lfnBase, mergedLFN,
+                                 "Error: Incorrect unmerged LFN %s." % outputModule.lfnBase)
+                self.assertEqual(outputModule.mergedLFNBase, mergedLFN,
+                                 "Error: Incorrect merged LFN %s." % outputModule.mergedLFNBase)
+            else:
+                self.assertEqual(outputModule.lfnBase, unmergedLFN,
+                                 "Error: Incorrect unmerged LFN %s." % outputModule.lfnBase)
+                self.assertEqual(outputModule.mergedLFNBase, mergedLFN,
+                                 "Error: Incorrect merged LFN %s." % outputModule.mergedLFNBase)
+
+        outputDatasets = testWorkload.listOutputDatasets()
+        self.assertItemsEqual(outputDatasets, outDsets)
+        return
+
     def testUpdatingLFNAndDatasetMultipleVer(self):
         """
         _testUpdatingLFNAndDatasetMultipleVer_