modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/drift/DriftAgentService.java
| 27 +++
modules/core/client-api/src/main/java/org/rhq/core/clientapi/server/drift/DriftServerService.java
| 77 ++++++++--
modules/core/plugin-container/src/main/java/org/rhq/core/pc/drift/DriftManager.java
| 42 +++--
modules/core/plugin-container/src/test/java/org/rhq/core/pc/drift/DriftManagerTest.java
| 32 +---
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftFileBean.java
| 4
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerBean.java
| 18 ++
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerLocal.java
| 7
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftServerServiceImpl.java
| 5
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftUploadRequest.java
| 19 ++
modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/drift/DriftManagerBeanTest.java
| 4
modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/test/TestAgentClient.java
| 4
11 files changed, 189 insertions(+), 50 deletions(-)
New commits:
commit 339d435bb20f550567ec68d87eeb2fbb8171a98b
Author: John Sanda <jsanda(a)redhat.com>
Date: Wed Sep 28 10:12:19 2011 -0400
Server now sends acknowledgement to agent when change set content is persisted
This commit adds some error handling around sending change set content
from agent to server. Previously, if the server failed to receive or
persist content, the agent would never know. Now the server sends an
explicit ackowledgement.
In a future commit, I will add support for resending content during
inventory sync for anything that has not been acked.
diff --git
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/drift/DriftAgentService.java
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/drift/DriftAgentService.java
index 6d39431..d58c766 100644
---
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/drift/DriftAgentService.java
+++
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/agent/drift/DriftAgentService.java
@@ -104,5 +104,32 @@ public interface DriftAgentService {
*/
void updateDriftDetection(int resourceId, DriftConfiguration driftConfiguration);
+ /**
+ * This method is invoked to inform the agent that the server has successfully
+ * persisted the last change set sent to the server for the specified drift
+ * configuration. The agent will proceed to purge the older, previous snapshot
+ * as well as the the change set zip file.
+ * <br/><br/>
+ * <strong>Note:</strong> The agent will not resume drift detection for
this drift
+ * configuration until the server has acknowledged the last change set sent.
+ *
+ * @param resourceId The id of the resource to which the acknowledged change set
+ * belongs.
+ * @param driftConfigName The name of the drift configuration to which the change
set
+ * belongs.
+ */
void ackChangeSet(int resourceId, String driftConfigName);
+
+ /**
+ * This method is invoked to inform the agent that the server has successfully
+ * persisted change set content that was previously sent to the server. The agent
+ * will proceed to purge the referenced content zip file.
+ *
+ * @param resourceId The id of the resource to which the referenced drift
configuration
+ * belongs.
+ * @param driftConfigName The name of the drift configuration to which the content
+ * belongs.
+ * @param token A token that is used to identify the content zip file
+ */
+ void ackChangeSetContent(int resourceId, String driftConfigName, String token);
}
\ No newline at end of file
diff --git
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/server/drift/DriftServerService.java
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/server/drift/DriftServerService.java
index c31514d..4f1deef 100644
---
a/modules/core/client-api/src/main/java/org/rhq/core/clientapi/server/drift/DriftServerService.java
+++
b/modules/core/client-api/src/main/java/org/rhq/core/clientapi/server/drift/DriftServerService.java
@@ -29,6 +29,9 @@ import org.rhq.core.domain.drift.DriftConfiguration;
import org.rhq.core.domain.drift.DriftSnapshot;
/**
+ * This class defines the drift server API that the agent uses for things like sending
+ * change set reports, sending change set content, and performing inventory sync.
+ *
* @author Jay Shaughnessy
* @author John Sanda
*/
@@ -37,26 +40,82 @@ public interface DriftServerService {
// note that this guaranteed delivery is weak because most likely the stream will be
dead if it
// doesn't work the first time.
/**
- * The ChangeSet file is of the format described in ChangeSetReader.
+ * The agent invokes this method to send a change set in a zip file. The agent will
call
+ * this method to report the initial change set when starting drift detection and
+ * subsequently whenever drift is detected. The format of the change set report is
+ * described in {@link org.rhq.common.drift.ChangeSetWriter ChangeSetWriter}. This
+ * method starts the following work flow:
+ * <ul>
+ * <li>
+ * A request is sent to JMS queue so that the change set can be processed
+ * asynchronously allowing control to return back to the agent as quickly as
+ * possible. It also allows the server to be able to more quickly service other
+ * agent and/or web requests.
+ * </li>
+ * <li>The server streams and persists the change set.</li>
+ * <li>
+ * The server sends an acknowledgement to the agent to let it know that the
change
+ * set was successfully persisted. The agent will refrain from sending any more
+ * change sets until this step has completed.
+ * </li>
+ * <li>
+ * The server sends a request to the agent for any content referenced in the
change
+ * set that is not yet in the database.
+ * </li>
+ * </ul>
+ *
+ * Note that because the change set bits are streamed out of band, if any errors
occur
+ * during the streaming, exception will not be propagated back up the call stack on
the
+ * agent side in the thread that initiated the call. This is the reason for the
+ * acknowledgement step.
*
- * @param resourceId
- * @param zipSize
- * @param zipStream A RemoteStream
+ * @param resourceId The id of the resource to which this change set belongs. This
+ * parameter may obsolete since the resource id is specified in the change set
headers.
+ * @param zipSize The total number of bytes to be streamed
+ * @param zipStream A RemoteInputStream
*/
+ // TODO is the resourceId param needed since it is also included in the change set
headers?
void sendChangesetZip(int resourceId, long zipSize, InputStream zipStream);
// note that this guaranteed delivery is weak because most likely the stream will be
dead if it
// doesn't work the first time.
/**
- * The name of each zip entry should be the sha256. The filenames and paths are not
relevant as this
- * is only a store of content and the content is identified ony by the sha.
+ * The agent invokes this method to send change set content to the server. The
content
+ * are files referenced in a change set that the agent previously sent to the
server.
+ * The zip file is assumed to be flat, that is it contains only files, no
directories.
+ * The name of each file should be the SHA hash of the file contents. File names and
+ * paths are not relevant as the server only stores content which is identified by
the
+ * SHA hashes. This method starts the following work flow:
+ * <ul>
+ * <li>
+ * A request is sent to a JMS queue so that the work can be performed
asynchronously
+ * allowing control to return back to the agent as quickly as possible. It also
+ * allows the server to more quickly service other agent and/or web requests.
+ * </li>
+ * <li>The server streams and persists each file.</li>
+ * <li>
+ * The server sends an acknowledgement to the agent to let it know that the
content
+ * has been successfully persisted.
+ * </li>
+ * </ul>
+ *
+ * Note that that the guaranteed delivery on this method is weak due to the
streaming
+ * being done asynchronously. If the first attempt at streaming fails the stream
will
+ * likey be dead and subsequent retries will fail as well. Because the streaming is
+ * done out of band, any network IO errors will not be propagated back up the call
stack
+ * on the agent side in the thread that initiated this method call. This is the
reaosn
+ * for the acknowledgement step.
*
- * @param resourceId
- * @param zipSize
+ * @param resourceId The id of the resource to which the change set content belongs
+ * @param driftConfigName The drift configuration name. This is needed for the
+ * acknowledgement step.
+ * @param token A token needed for the acknowledgement step that allows the agent to
+ * uniquely identify the zip file that was sent.
+ * @param zipSize The total number of bytes to be streamed.
* @param zipStream A RemoteStream
*/
@Asynchronous(guaranteedDelivery = true)
- void sendFilesZip(int resourceId, long zipSize, InputStream zipStream);
+ void sendFilesZip(int resourceId, String driftConfigName, String token, long zipSize,
InputStream zipStream);
Map<Integer, List<DriftConfiguration>>
getDriftConfigurations(Set<Integer> resourceIds);
diff --git
a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/drift/DriftManager.java
b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/drift/DriftManager.java
index 400a63a..ab83351 100644
--- a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/drift/DriftManager.java
+++ b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/drift/DriftManager.java
@@ -325,7 +325,8 @@ public class DriftManager extends AgentService implements
DriftAgentService, Dri
@Override
public void sendChangeSetContentToServer(int resourceId, String
driftConfigurationName, final File contentDir) {
try {
- String contentFileName = "content_" + System.currentTimeMillis() +
".zip";
+ String timestamp = Long.toString(System.currentTimeMillis());
+ String contentFileName = "content_" + timestamp +
".zip";
final File zipFile = new File(contentDir.getParentFile(), contentFileName);
ZipOutputStream stream = new ZipOutputStream(new BufferedOutputStream(new
FileOutputStream(zipFile)));
@@ -337,19 +338,9 @@ public class DriftManager extends AgentService implements
DriftAgentService, Dri
}
stream.close();
- // We want to clean up after we send the zip file to the server. We do this
by
- // deleting the files in the content directory and the content zip itself.
They
- // are no longer needed after being sent to the server. We cannot
immediately
- // delete the content zip file though because it is sent asynchronously, and
we
- // wind up deleting it before it is sent. The following approach allows us
to
- // safely delete it when the comm layer closes the remote input stream.
- //
- // jsanda
- DriftInputStream inputStream = new DriftInputStream(new
BufferedInputStream(new FileInputStream(zipFile)),
- new DeleteFile(zipFile));
-
DriftServerService driftServer =
pluginContainerConfiguration.getServerServices().getDriftServerService();
- driftServer.sendFilesZip(resourceId, zipFile.length(),
remoteInputStream(inputStream));
+ driftServer.sendFilesZip(resourceId, driftConfigurationName, timestamp,
zipFile.length(),
+ remoteInputStream(new BufferedInputStream(new
FileInputStream(zipFile))));
} catch (IOException e) {
log.error("An error occurred while trying to send content for
changeset[resourceId: " + resourceId
+ ", driftConfiguration: " + driftConfigurationName +
"]", e);
@@ -503,7 +494,7 @@ public class DriftManager extends AgentService implements
DriftAgentService, Dri
File changeSetDir = new File(resourceDir, driftConfigName);
if (!changeSetDir.exists()) {
- log.warn("Cannot ack change set. Change set directory " +
changeSetDir.getPath() +
+ log.warn("Cannot complete acknowledgement. Change set directory " +
changeSetDir.getPath() +
" does not exist.");
return;
}
@@ -512,14 +503,31 @@ public class DriftManager extends AgentService implements
DriftAgentService, Dri
File previousSnapshot = new File(snapshot.getParentFile(), snapshot.getName() +
".previous");
previousSnapshot.delete();
- deleteZipFiles(changeSetDir);
+ deleteZipFiles(changeSetDir, "changeset_");
+ }
+
+ @Override
+ public void ackChangeSetContent(int resourceId, String driftConfigName, String token)
{
+ log.info("Received server change set content ack for [resourceId: " +
resourceId +
+ ", driftConfigurationName: " + driftConfigName + "]");
+
+ File resourceDir = new File(changeSetsDir, Integer.toString(resourceId));
+ File changeSetDir = new File(resourceDir, driftConfigName);
+
+ if (!changeSetDir.exists()) {
+ log.warn("Cannot complete acknowledgement. Change set directory " +
changeSetDir.getPath() +
+ " does not exist.");
+ return;
+ }
+
+ deleteZipFiles(changeSetDir, "content_" + token);
}
- private void deleteZipFiles(File dir) {
+ private void deleteZipFiles(File dir, final String prefix) {
File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
- return name.endsWith(".zip");
+ return name.startsWith(prefix) &&
name.endsWith(".zip");
}
});
for (File file : files) {
diff --git
a/modules/core/plugin-container/src/test/java/org/rhq/core/pc/drift/DriftManagerTest.java
b/modules/core/plugin-container/src/test/java/org/rhq/core/pc/drift/DriftManagerTest.java
index cac5ea8..ae3a940 100644
---
a/modules/core/plugin-container/src/test/java/org/rhq/core/pc/drift/DriftManagerTest.java
+++
b/modules/core/plugin-container/src/test/java/org/rhq/core/pc/drift/DriftManagerTest.java
@@ -183,30 +183,23 @@ public class DriftManagerTest extends DriftTest {
});
driftMgr.sendChangeSetContentToServer(resourceId(), configName, contentDir);
+
+ // verify that the content directory is purged
+ assertThatDirectoryIsEmpty(contentDir);
}
@Test
- public void cleanUpAfterSendingContentToServer() throws Exception {
- String configName = "clean-up-after-sending-content";
+ public void cleanUpWhenServerAcksChangeSetContent() throws Exception {
+ String configName = "cleanup-when-server-acks-content";
File changeSetDir = changeSetDir(configName);
File contentDir = mkdir(changeSetDir, "content");
- createRandomFile(contentDir, "content-1");
- createRandomFile(contentDir, "content-2");
-
- driftMgr.sendChangeSetContentToServer(resourceId(), configName, contentDir);
-
- // clean up should not happen until after the input stream is closed. The remote
- // input stream is consumed asynchronously; so, we have to wait until the stream
- // is closed. We could otherwise disrupt the transmission of bits.
- File contentZipFile = assertThatZipFileExists(changeSetDir,
"content_",
- "Expected to find content zip file in " +
- changeSetDir.getPath() + ". The file name should have a pattern of
content_integer_timestamp>.zip");
+ String token = Long.toString(System.currentTimeMillis());
+ File contentZipFile = createRandomFile(changeSetDir, "content_" + token
+ ".zip");
- driftServerService.inputStream.close();
+ driftMgr.ackChangeSetContent(resourceId(), configName, token);
- assertFalse(contentZipFile.exists(), "The content zip should be deleted when
the remote input stream is closed");
- assertThatDirectoryIsEmpty(contentDir);
+ assertFalse(contentZipFile.exists(), "Content zip file should be purged
after server sends content ack");
}
@Test
@@ -367,6 +360,8 @@ public class DriftManagerTest extends DriftTest {
private static class TestDriftServerService implements DriftServerService {
public int resourceId;
+ public String driftConfigName;
+ public String token;
public long fileSize;
public InputStream inputStream;
@@ -384,8 +379,11 @@ public class DriftManagerTest extends DriftTest {
}
@Override
- public void sendFilesZip(int resourceId, long zipSize, InputStream zipStream) {
+ public void sendFilesZip(int resourceId, String driftConfigName, String token,
long zipSize,
+ InputStream zipStream) {
this.resourceId = resourceId;
+ this.driftConfigName = driftConfigName;
+ this.token = token;
fileSize = zipSize;
inputStream = zipStream;
diff --git
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftFileBean.java
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftFileBean.java
index 6c639e4..4d7bebf 100644
---
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftFileBean.java
+++
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftFileBean.java
@@ -84,8 +84,8 @@ public class DriftFileBean implements MessageListener {
+ "]");
}
- driftManager.saveChangeSetFiles(subjectManager.getOverlord(), tempFile);
-
+ driftManager.saveChangeSetContent(subjectManager.getOverlord(),
request.getResourceId(),
+ request.getDriftConfigName(), request.getToken(), tempFile);
} catch (IOException e) {
log.error(e);
diff --git
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerBean.java
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerBean.java
index d848e1b..aafd0bb 100644
---
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerBean.java
+++
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerBean.java
@@ -27,6 +27,7 @@ import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageProducer;
@@ -194,17 +195,30 @@ public class DriftManagerBean implements DriftManagerLocal,
DriftManagerRemote {
//
http://management-platform.blogspot.com/2008/11/transaction-recovery-in-j...
@Override
@TransactionAttribute(REQUIRES_NEW)
- public void addFiles(int resourceId, long zipSize, InputStream zipStream) throws
Exception {
+ public void addFiles(int resourceId, String driftConfigName, String token, long
zipSize, InputStream zipStream)
+ throws Exception {
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(fileQueue);
- ObjectMessage msg = session.createObjectMessage(new
DriftUploadRequest(resourceId, zipSize, zipStream));
+ ObjectMessage msg = session.createObjectMessage(new
DriftUploadRequest(resourceId, driftConfigName, token,
+ zipSize, zipStream));
producer.send(msg);
connection.close();
}
@Override
+ @TransactionAttribute(TransactionAttributeType.SUPPORTS)
+ public void saveChangeSetContent(Subject subject, int resourceId, String
driftConfigName, String token,
+ File changeSetFilesZip) throws Exception {
+ saveChangeSetFiles(subject, changeSetFilesZip);
+
+ AgentClient agent = agentManager.getAgentClient(subjectManager.getOverlord(),
resourceId);
+ DriftAgentService driftService = agent.getDriftAgentService();
+ driftService.ackChangeSetContent(resourceId, driftConfigName, token);
+ }
+
+ @Override
@TransactionAttribute(NOT_SUPPORTED)
public DriftSnapshot createSnapshot(Subject subject, DriftChangeSetCriteria criteria)
{
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
diff --git
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerLocal.java
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerLocal.java
index 9d8a883..c11482e 100644
---
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerLocal.java
+++
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftManagerLocal.java
@@ -19,6 +19,7 @@
*/
package org.rhq.enterprise.server.drift;
+import java.io.File;
import java.io.InputStream;
import javax.ejb.Local;
@@ -57,7 +58,11 @@ public interface DriftManagerLocal extends DriftServerPluginFacet,
DriftManagerR
* @param zipStream The drift files zip file stream
* @throws Exception
*/
- void addFiles(int resourceId, long zipSize, InputStream zipStream) throws Exception;
+ void addFiles(int resourceId, String driftConfigName, String token, long zipSize,
InputStream zipStream)
+ throws Exception;
+
+ void saveChangeSetContent(Subject subject, int resourceId, String driftConfigName,
String token,
+ File changeSetFilesZip) throws Exception;
/**
* Remove the provided driftConfig (identified by name) on the specified
entityContext.
diff --git
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftServerServiceImpl.java
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftServerServiceImpl.java
index 7af5163..c017ca3 100644
---
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftServerServiceImpl.java
+++
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftServerServiceImpl.java
@@ -51,10 +51,11 @@ public class DriftServerServiceImpl implements DriftServerService {
}
@Override
- public void sendFilesZip(int resourceId, long zipSize, InputStream zipStream) {
+ public void sendFilesZip(int resourceId, String driftConfigName, String token, long
zipSize,
+ InputStream zipStream) {
try {
DriftManagerLocal driftManager = getDriftManager();
- driftManager.addFiles(resourceId, zipSize, zipStream);
+ driftManager.addFiles(resourceId, driftConfigName, token, zipSize,
zipStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftUploadRequest.java
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftUploadRequest.java
index 6ef7ba2..3c5a31e 100644
---
a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftUploadRequest.java
+++
b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/drift/DriftUploadRequest.java
@@ -27,12 +27,23 @@ public class DriftUploadRequest implements Serializable {
private int resourceId;
+ private String driftConfigName;
+
+ private String token;
+
private long dataSize;
private InputStream dataStream;
public DriftUploadRequest(int resourceId, long dataSize, InputStream dataStream) {
+ this(resourceId, null, null, dataSize, dataStream);
+ }
+
+ public DriftUploadRequest(int resourceId, String driftConfigName, String token, long
dataSize,
+ InputStream dataStream) {
this.resourceId = resourceId;
+ this.driftConfigName = driftConfigName;
+ this.token = token;
this.dataSize = dataSize;
this.dataStream = dataStream;
}
@@ -41,6 +52,14 @@ public class DriftUploadRequest implements Serializable {
return resourceId;
}
+ public String getDriftConfigName() {
+ return driftConfigName;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
public long getDataSize() {
return dataSize;
}
diff --git
a/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/drift/DriftManagerBeanTest.java
b/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/drift/DriftManagerBeanTest.java
index 244d332..ab84ce5 100644
---
a/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/drift/DriftManagerBeanTest.java
+++
b/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/drift/DriftManagerBeanTest.java
@@ -451,5 +451,9 @@ public class DriftManagerBeanTest extends AbstractEJB3Test {
@Override
public void ackChangeSet(int resourceId, String driftConfigName) {
}
+
+ @Override
+ public void ackChangeSetContent(int resourceId, String driftConfigName, String
token) {
+ }
}
}
\ No newline at end of file
diff --git
a/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/test/TestAgentClient.java
b/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/test/TestAgentClient.java
index 5940b6a..43d0ded 100644
---
a/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/test/TestAgentClient.java
+++
b/modules/enterprise/server/jar/src/test/java/org/rhq/enterprise/server/test/TestAgentClient.java
@@ -354,4 +354,8 @@ public class TestAgentClient implements AgentClient,
BundleAgentService, DriftAg
@Override
public void ackChangeSet(int resourceId, String driftConfigName) {
}
+
+ @Override
+ public void ackChangeSetContent(int resourceId, String driftConfigName, String token)
{
+ }
}