This is an automated email from the ASF dual-hosted git repository.
sodonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new f6bb53671fe HDDS-13763. Ozone Recon - Return empty path when NSSummary
tree parent Id not found while constructing full path of FSO key. (#9121)
f6bb53671fe is described below
commit f6bb53671fe07a3274a8774194b624f1679becc2
Author: Devesh Kumar Singh <[email protected]>
AuthorDate: Thu Oct 9 16:26:04 2025 +0530
HDDS-13763. Ozone Recon - Return empty path when NSSummary tree parent Id
not found while constructing full path of FSO key. (#9121)
---
.../org/apache/hadoop/ozone/recon/ReconUtils.java | 41 ++++++++++------------
.../hadoop/ozone/recon/api/ContainerEndpoint.java | 2 +-
.../ozone/recon/api/OMDBInsightEndpoint.java | 26 +++++++++-----
.../recon/api/TestNSSummaryEndpointWithFSO.java | 24 ++++++-------
.../recon/api/TestNSSummaryEndpointWithLegacy.java | 6 ++--
.../api/TestNSSummaryEndpointWithOBSAndLegacy.java | 8 ++---
.../ozone/recon/api/TestOmDBInsightEndPoint.java | 4 +--
7 files changed, 58 insertions(+), 53 deletions(-)
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java
index 2758dfb34cc..ccc92648f11 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java
@@ -71,7 +71,6 @@
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
-import org.apache.hadoop.ozone.recon.api.ServiceNotReadyException;
import org.apache.hadoop.ozone.recon.api.handlers.BucketHandler;
import org.apache.hadoop.ozone.recon.api.handlers.EntityHandler;
import org.apache.hadoop.ozone.recon.api.types.DUResponse;
@@ -180,7 +179,7 @@ public void untarCheckpointFile(File tarFile, Path destPath)
/**
* Constructs the full path of a key from its OmKeyInfo using a bottom-up
approach, starting from the leaf node.
- *
+ * <p>
* The method begins with the leaf node (the key itself) and recursively
prepends parent directory names, fetched
* via NSSummary objects, until reaching the parent bucket (parentId is -1).
It effectively builds the path from
* bottom to top, finally prepending the volume and bucket names to complete
the full path. If the directory structure
@@ -189,39 +188,37 @@ public void untarCheckpointFile(File tarFile, Path
destPath)
*
* @param omKeyInfo The OmKeyInfo object for the key
* @return The constructed full path of the key as a String, or an empty
string if a rebuild is in progress and
- * the path cannot be constructed at this time.
+ * the path cannot be constructed at this time.
* @throws IOException
*/
public static String constructFullPath(OmKeyInfo omKeyInfo,
- ReconNamespaceSummaryManager
reconNamespaceSummaryManager,
- ReconOMMetadataManager
omMetadataManager) throws IOException {
+ ReconNamespaceSummaryManager
reconNamespaceSummaryManager) throws IOException {
return constructFullPath(omKeyInfo.getKeyName(),
omKeyInfo.getParentObjectID(), omKeyInfo.getVolumeName(),
- omKeyInfo.getBucketName(), reconNamespaceSummaryManager,
omMetadataManager);
+ omKeyInfo.getBucketName(), reconNamespaceSummaryManager);
}
/**
* Constructs the full path of a key from its key name and parent ID using a
bottom-up approach, starting from the
* leaf node.
- *
+ * <p>
* The method begins with the leaf node (the key itself) and recursively
prepends parent directory names, fetched
* via NSSummary objects, until reaching the parent bucket (parentId is -1).
It effectively builds the path from
* bottom to top, finally prepending the volume and bucket names to complete
the full path. If the directory structure
* is currently being rebuilt (indicated by the rebuildTriggered flag), this
method returns an empty string to signify
* that path construction is temporarily unavailable.
*
- * @param keyName The name of the key
+ * @param keyName The name of the key
* @param initialParentId The parent ID of the key
- * @param volumeName The name of the volume
- * @param bucketName The name of the bucket
+ * @param volumeName The name of the volume
+ * @param bucketName The name of the bucket
* @return The constructed full path of the key as a String, or an empty
string if a rebuild is in progress and
- * the path cannot be constructed at this time.
+ * the path cannot be constructed at this time.
* @throws IOException
*/
public static String constructFullPath(String keyName, long initialParentId,
String volumeName, String bucketName,
- ReconNamespaceSummaryManager
reconNamespaceSummaryManager,
- ReconOMMetadataManager
omMetadataManager) throws IOException {
+ ReconNamespaceSummaryManager
reconNamespaceSummaryManager) throws IOException {
StringBuilder fullPath = constructFullPathPrefix(initialParentId,
volumeName, bucketName,
- reconNamespaceSummaryManager, omMetadataManager);
+ reconNamespaceSummaryManager);
if (fullPath.length() == 0) {
return "";
}
@@ -232,7 +229,7 @@ public static String constructFullPath(String keyName, long
initialParentId, Str
/**
* Constructs the prefix path to a key from its key name and parent ID using
a bottom-up approach, starting from the
* leaf node.
- *
+ * <p>
* The method begins with the leaf node (the key itself) and recursively
prepends parent directory names, fetched
* via NSSummary objects, until reaching the parent bucket (parentId is -1).
It effectively builds the path from
* bottom to top, finally prepending the volume and bucket names to complete
the full path. If the directory structure
@@ -240,16 +237,16 @@ public static String constructFullPath(String keyName,
long initialParentId, Str
* that path construction is temporarily unavailable.
*
* @param initialParentId The parent ID of the key
- * @param volumeName The name of the volume
- * @param bucketName The name of the bucket
+ * @param volumeName The name of the volume
+ * @param bucketName The name of the bucket
* @return A StringBuilder containing the constructed prefix path of the
key, or an empty string builder if a rebuild
- * is in progress.
+ * is in progress.
* @throws IOException
*/
public static StringBuilder constructFullPathPrefix(long initialParentId,
String volumeName,
- String bucketName, ReconNamespaceSummaryManager
reconNamespaceSummaryManager,
- ReconOMMetadataManager omMetadataManager) throws IOException {
+ String bucketName, ReconNamespaceSummaryManager
reconNamespaceSummaryManager) throws IOException {
+ StringBuilder fullPath = new StringBuilder();
long parentId = initialParentId;
boolean isDirectoryPresent = false;
@@ -259,7 +256,8 @@ public static StringBuilder constructFullPathPrefix(long
initialParentId, String
if (nsSummary == null) {
log.warn("NSSummary tree is currently being rebuilt or the directory
could be in the progress of " +
"deletion, returning empty string for path construction.");
- throw new ServiceNotReadyException("Service is initializing. Please
try again later.");
+ fullPath.setLength(0);
+ return fullPath;
}
// On the last pass, dir-name will be empty and parent will be zero,
indicating the loop should end.
if (!nsSummary.getDirName().isEmpty()) {
@@ -271,7 +269,6 @@ public static StringBuilder constructFullPathPrefix(long
initialParentId, String
isDirectoryPresent = true;
}
- StringBuilder fullPath = new StringBuilder();
fullPath.append(volumeName).append(OM_KEY_PREFIX)
.append(bucketName).append(OM_KEY_PREFIX);
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
index 89db23520de..49fab38fcf2 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
@@ -285,7 +285,7 @@ public Response getKeysForContainer(
keyMetadata.setVolume(omKeyInfo.getVolumeName());
keyMetadata.setKey(omKeyInfo.getKeyName());
keyMetadata.setCompletePath(ReconUtils.constructFullPath(omKeyInfo,
- reconNamespaceSummaryManager, omMetadataManager));
+ reconNamespaceSummaryManager));
keyMetadata.setCreationTime(
Instant.ofEpochMilli(omKeyInfo.getCreationTime()));
keyMetadata.setModificationTime(
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/OMDBInsightEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/OMDBInsightEndpoint.java
index 2a4ed2bd5fa..7e2e57879ed 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/OMDBInsightEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/OMDBInsightEndpoint.java
@@ -516,7 +516,6 @@ private boolean getPendingForDeletionKeyInfo(
// We know each RepeatedOmKeyInfo has just one OmKeyInfo object
OmKeyInfo keyInfo = repeatedOmKeyInfo.getOmKeyInfoList().get(0);
- KeyEntityInfo keyEntityInfo =
createKeyEntityInfoFromOmKeyInfo(entry.getKey(), keyInfo);
// Add the key directly to the list without classification
deletedKeyInsightInfo.getRepeatedOmKeyInfoList().add(repeatedOmKeyInfo);
@@ -1222,9 +1221,14 @@ private void retrieveKeysFromTable(
// Legacy buckets are obsolete, so this code path is not
optimized. We don't expect to see many Legacy
// buckets in practice.
prevParentID = -1;
-
keyEntityInfo.setPath(ReconUtils.constructFullPath(keyEntityInfo.getKeyName(),
keyEntityInfo.getParentId(),
- keyEntityInfo.getVolumeName(), keyEntityInfo.getBucketName(),
reconNamespaceSummaryManager,
- omMetadataManager));
+ String fullPath =
ReconUtils.constructFullPath(keyEntityInfo.getKeyName(),
keyEntityInfo.getParentId(),
+ keyEntityInfo.getVolumeName(), keyEntityInfo.getBucketName(),
reconNamespaceSummaryManager);
+ if (fullPath.isEmpty()) {
+ LOG.warn("Full path is empty for volume: {}, bucket: {}, key:
{}",
+ keyEntityInfo.getVolumeName(),
keyEntityInfo.getBucketName(), keyEntityInfo.getKeyName());
+ continue;
+ }
+ keyEntityInfo.setPath(fullPath);
} else {
// As we iterate keys in sorted order, its highly likely that keys
have the same prefix for many keys in a
// row. Especially for FSO buckets, its expensive to construct the
path for each key. So, we construct the
@@ -1233,13 +1237,18 @@ private void retrieveKeysFromTable(
if (prevParentID != keyEntityInfo.getParentId()) {
prevParentID = keyEntityInfo.getParentId();
keyPrefix =
ReconUtils.constructFullPathPrefix(keyEntityInfo.getParentId(),
- keyEntityInfo.getVolumeName(),
keyEntityInfo.getBucketName(), reconNamespaceSummaryManager,
- omMetadataManager);
+ keyEntityInfo.getVolumeName(),
keyEntityInfo.getBucketName(), reconNamespaceSummaryManager);
keyPrefixLength = keyPrefix.length();
}
keyPrefix.setLength(keyPrefixLength);
keyPrefix.append(keyEntityInfo.getKeyName());
- keyEntityInfo.setPath(keyPrefix.toString());
+ String keyPrefixFullPath = keyPrefix.toString();
+ if (keyPrefixFullPath.isEmpty()) {
+ LOG.warn("Full path is empty for volume: {}, bucket: {}, key:
{}",
+ keyEntityInfo.getVolumeName(),
keyEntityInfo.getBucketName(), keyEntityInfo.getKeyName());
+ continue;
+ }
+ keyEntityInfo.setPath(keyPrefixFullPath);
}
results.add(keyEntityInfo);
@@ -1286,7 +1295,8 @@ private KeyEntityInfo
createKeyEntityInfoFromOmKeyInfo(String dbKey,
KeyEntityInfo keyEntityInfo = new KeyEntityInfo();
keyEntityInfo.setKey(dbKey); // Set the DB key
keyEntityInfo.setIsKey(keyInfo.isFile());
- keyEntityInfo.setPath(ReconUtils.constructFullPath(keyInfo,
reconNamespaceSummaryManager, omMetadataManager));
+ String fullKeyPath = ReconUtils.constructFullPath(keyInfo,
reconNamespaceSummaryManager);
+ keyEntityInfo.setPath(fullKeyPath.isEmpty() ? keyInfo.getKeyName() :
fullKeyPath);
keyEntityInfo.setSize(keyInfo.getDataSize());
keyEntityInfo.setCreationTime(keyInfo.getCreationTime());
keyEntityInfo.setModificationTime(keyInfo.getModificationTime());
diff --git
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java
index 227330c83f6..05b6b5f300e 100644
---
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java
+++
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java
@@ -28,7 +28,6 @@
import static
org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_NSSUMMARY_FLUSH_TO_DB_MAX_THRESHOLD;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -707,7 +706,7 @@ public void testConstructFullPath() throws IOException {
.build();
// Call constructFullPath and verify the result
String fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
String expectedPath = "vol/bucket1/dir1/dir2/file2";
Assertions.assertEquals(expectedPath, fullPath);
@@ -720,7 +719,7 @@ public void testConstructFullPath() throws IOException {
.setParentObjectID(DIR_THREE_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol/bucket1/dir1/dir3/file3";
Assertions.assertEquals(expectedPath, fullPath);
@@ -733,7 +732,7 @@ public void testConstructFullPath() throws IOException {
.setParentObjectID(DIR_FOUR_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol/bucket1/dir1/dir4/file6";
Assertions.assertEquals(expectedPath, fullPath);
@@ -746,7 +745,7 @@ public void testConstructFullPath() throws IOException {
.setParentObjectID(BUCKET_ONE_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol/bucket1/file1";
Assertions.assertEquals(expectedPath, fullPath);
@@ -759,7 +758,7 @@ public void testConstructFullPath() throws IOException {
.setParentObjectID(DIR_FIVE_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol2/bucket3/dir5/file9";
Assertions.assertEquals(expectedPath, fullPath);
@@ -781,10 +780,9 @@ public void testConstructFullPath() throws IOException {
.setObjectID(KEY_TWO_OBJECT_ID)
.setParentObjectID(DIR_TWO_OBJECT_ID)
.build();
- // Call constructFullPath and verify the result
- OmKeyInfo finalKeyInfo = keyInfo;
- assertThrows(ServiceNotReadyException.class, () ->
ReconUtils.constructFullPath(finalKeyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager));
+ // Call constructFullPath and verify the result - should return empty
string when NSSummary parent is invalid
+ fullPath = ReconUtils.constructFullPath(keyInfo,
reconNamespaceSummaryManager);
+ Assertions.assertEquals("", fullPath, "Should return empty string when
NSSummary tree is being rebuilt");
}
@Test
@@ -792,7 +790,6 @@ public void
testConstructFullPathWithNegativeParentIdTriggersRebuild() throws IO
// Setup
long dirOneObjectId = 1L; // Sample object ID for the directory
ReconNamespaceSummaryManager mockSummaryManager =
mock(ReconNamespaceSummaryManager.class);
- ReconOMMetadataManager mockMetadataManager =
mock(ReconOMMetadataManager.class);
NSSummary dir1Summary = new NSSummary();
dir1Summary.setParentId(-1); // Simulate directory at the top of the tree
when(mockSummaryManager.getNSSummary(dirOneObjectId)).thenReturn(dir1Summary);
@@ -805,8 +802,9 @@ public void
testConstructFullPathWithNegativeParentIdTriggersRebuild() throws IO
.setParentObjectID(dirOneObjectId)
.build();
- assertThrows(ServiceNotReadyException.class, () ->
- ReconUtils.constructFullPath(keyInfo, mockSummaryManager,
mockMetadataManager));
+ // Should return empty string when NSSummary has invalid parentId
+ String fullPath = ReconUtils.constructFullPath(keyInfo,
mockSummaryManager);
+ Assertions.assertEquals("", fullPath, "Should return empty string when
NSSummary has negative parentId");
}
/**
diff --git
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java
index 01881221979..6973a67930f 100644
---
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java
+++
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java
@@ -704,7 +704,7 @@ public void testConstructFullPath() throws IOException {
.build();
// Call constructFullPath and verify the result
String fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
String expectedPath = "vol/bucket1/dir1/dir2/file2";
Assertions.assertEquals(expectedPath, fullPath);
@@ -716,7 +716,7 @@ public void testConstructFullPath() throws IOException {
.setObjectID(DIR_TWO_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol/bucket1/dir1/dir2/";
Assertions.assertEquals(expectedPath, fullPath);
@@ -728,7 +728,7 @@ public void testConstructFullPath() throws IOException {
.setObjectID(KEY_SIX_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol/bucket1/dir1/dir4/file6";
Assertions.assertEquals(expectedPath, fullPath);
}
diff --git
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithOBSAndLegacy.java
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithOBSAndLegacy.java
index 9a311fb07e2..eefc1194060 100644
---
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithOBSAndLegacy.java
+++
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithOBSAndLegacy.java
@@ -901,7 +901,7 @@ public void testConstructFullPath() throws IOException {
.setObjectID(KEY_TWO_OBJECT_ID)
.build();
String fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
String expectedPath = "vol/bucket1/" + KEY_TWO;
Assertions.assertEquals(expectedPath, fullPath);
@@ -912,7 +912,7 @@ public void testConstructFullPath() throws IOException {
.setObjectID(KEY_FIVE_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol/bucket2/" + KEY_FIVE;
Assertions.assertEquals(expectedPath, fullPath);
@@ -923,7 +923,7 @@ public void testConstructFullPath() throws IOException {
.setObjectID(KEY_EIGHT_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol2/bucket3/" + KEY_EIGHT;
Assertions.assertEquals(expectedPath, fullPath);
@@ -935,7 +935,7 @@ public void testConstructFullPath() throws IOException {
.setObjectID(KEY_ELEVEN_OBJECT_ID)
.build();
fullPath = ReconUtils.constructFullPath(keyInfo,
- reconNamespaceSummaryManager, reconOMMetadataManager);
+ reconNamespaceSummaryManager);
expectedPath = "vol2/bucket4/" + KEY_ELEVEN;
Assertions.assertEquals(expectedPath, fullPath);
}
diff --git
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestOmDBInsightEndPoint.java
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestOmDBInsightEndPoint.java
index 8946561c632..22b649dd986 100644
---
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestOmDBInsightEndPoint.java
+++
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestOmDBInsightEndPoint.java
@@ -1921,8 +1921,8 @@ public void testListKeysWhenNSSummaryNotInitialized()
throws Exception {
omdbInsightEndpoint.listKeys("RATIS", "", 0, FSO_BUCKET_TWO_PATH,
"", 1000);
ListKeysResponse listKeysResponse = (ListKeysResponse)
bucketResponse.getEntity();
- assertEquals(ResponseStatus.INITIALIZING, listKeysResponse.getStatus());
- assertEquals(Response.Status.SERVICE_UNAVAILABLE.getStatusCode(),
bucketResponse.getStatus());
+ assertEquals(ResponseStatus.OK, listKeysResponse.getStatus());
+ assertEquals(Response.Status.OK.getStatusCode(),
bucketResponse.getStatus());
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]