This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch ozone-2.0
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/ozone-2.0 by this push:
new 77e15c7b50b HDDS-15487. Reduce code duplication in Recon endpoints
77e15c7b50b is described below
commit 77e15c7b50b524942d42e4cb89c6af0b53ae228e
Author: Doroszlai, Attila <[email protected]>
AuthorDate: Mon Jun 22 13:35:11 2026 +0200
HDDS-15487. Reduce code duplication in Recon endpoints
(cherry picked from commit 95bda680b952d164706dc0b8debbcad805b6a32c)
---
.../dist/src/main/smoketest/recon/recon-api.robot | 60 +++++++++-----------
.../hadoop/ozone/recon/ReconRestServletModule.java | 57 +++++--------------
.../ozone/recon/api/AccessHeatMapEndpoint.java | 1 -
.../apache/hadoop/ozone/recon/api/AdminOnly.java | 34 -----------
.../hadoop/ozone/recon/api/BlocksEndPoint.java | 1 -
.../hadoop/ozone/recon/api/BucketEndpoint.java | 1 -
.../hadoop/ozone/recon/api/ContainerEndpoint.java | 1 -
.../hadoop/ozone/recon/api/FeaturesEndpoint.java | 1 -
.../hadoop/ozone/recon/api/NSSummaryEndpoint.java | 1 -
.../ozone/recon/api/OMDBInsightEndpoint.java | 1 -
.../ozone/recon/api/TriggerDBSyncEndpoint.java | 1 -
.../hadoop/ozone/recon/api/VolumeEndpoint.java | 1 -
.../ozone/recon/api/filters/TestAdminFilter.java | 66 ----------------------
13 files changed, 39 insertions(+), 187 deletions(-)
diff --git a/hadoop-ozone/dist/src/main/smoketest/recon/recon-api.robot
b/hadoop-ozone/dist/src/main/smoketest/recon/recon-api.robot
index 883e93bdc5c..aca4aac62ec 100644
--- a/hadoop-ozone/dist/src/main/smoketest/recon/recon-api.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/recon/recon-api.robot
@@ -25,9 +25,6 @@ Test Timeout 5 minutes
*** Variables ***
${ENDPOINT_URL} http://recon:9888
${API_ENDPOINT_URL} ${ENDPOINT_URL}/api/v1
-${ADMIN_API_ENDPOINT_URL} ${API_ENDPOINT_URL}/containers
-${UNHEALTHY_ENDPOINT_URL} ${API_ENDPOINT_URL}/containers/unhealthy
-${NON_ADMIN_API_ENDPOINT_URL} ${API_ENDPOINT_URL}/clusterState
${VOLUME} vol1
${BUCKET} bucket1
@@ -68,6 +65,23 @@ Check if the listKeys api responds OK
Should contain ${result} "${volume}"
Should contain ${result} "${bucket}"
+
+Verify admin-only API
+ [arguments] ${path}
+
+ Execute kdestroy
+ Check http return code ${API_ENDPOINT_URL}${path} 401
+
+ kinit as non admin
+ Check http return code ${API_ENDPOINT_URL}${path} 403
+
+ kinit as ozone admin
+ Check http return code ${API_ENDPOINT_URL}${path} 200
+
+ kinit as recon admin
+ Check http return code ${API_ENDPOINT_URL}${path} 200
+
+
*** Test Cases ***
Check if Recon picks up OM data
Execute ozone sh volume create recon
@@ -117,34 +131,12 @@ Check web UI access
Check http return code ${ENDPOINT_URL} 200
Check admin only api access
- Execute kdestroy
- Check http return code ${ADMIN_API_ENDPOINT_URL} 401
-
- kinit as non admin
- Check http return code ${ADMIN_API_ENDPOINT_URL} 403
-
- kinit as ozone admin
- Check http return code ${ADMIN_API_ENDPOINT_URL} 200
-
- kinit as recon admin
- Check http return code ${ADMIN_API_ENDPOINT_URL} 200
-
-Check unhealthy, (admin) api access
- Execute kdestroy
- Check http return code ${UNHEALTHY_ENDPOINT_URL} 401
-
- kinit as non admin
- Check http return code ${UNHEALTHY_ENDPOINT_URL} 403
-
- kinit as ozone admin
- Check http return code ${UNHEALTHY_ENDPOINT_URL} 200
-
- kinit as recon admin
- Check http return code ${UNHEALTHY_ENDPOINT_URL} 200
-
-Check normal api access
- Execute kdestroy
- Check http return code ${NON_ADMIN_API_ENDPOINT_URL} 401
-
- kinit as non admin
- Check http return code ${NON_ADMIN_API_ENDPOINT_URL} 200
+ Verify admin-only API /buckets
+ Verify admin-only API /clusterState
+ Verify admin-only API /containers
+ Verify admin-only API /datanodes
+ Verify admin-only API /keys/open/summary
+ Verify admin-only API /pipelines
+ Verify admin-only API /task/status
+ Verify admin-only API /utilization/fileCount
+ Verify admin-only API /volumes
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconRestServletModule.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconRestServletModule.java
index 9ec9621ed32..6cf17de1a08 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconRestServletModule.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconRestServletModule.java
@@ -25,13 +25,10 @@
import com.google.inject.servlet.ServletModule;
import java.net.URL;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
import javax.ws.rs.core.UriBuilder;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.ozone.OzoneSecurityUtil;
-import org.apache.hadoop.ozone.recon.api.AdminOnly;
import org.apache.hadoop.ozone.recon.api.filters.ReconAdminFilter;
import org.apache.hadoop.ozone.recon.api.filters.ReconAuthFilter;
import org.glassfish.hk2.api.ServiceLocator;
@@ -42,9 +39,6 @@
import org.glassfish.jersey.servlet.ServletContainer;
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
-import org.reflections.Reflections;
-import org.reflections.scanners.SubTypesScanner;
-import org.reflections.scanners.TypeAnnotationsScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,8 +47,7 @@
*/
public class ReconRestServletModule extends ServletModule {
- public static final String BASE_API_PATH = UriBuilder.fromPath("/api").path(
- "v1").build().toString();
+ public static final String BASE_API_PATH = "/api/v1";
public static final String API_PACKAGE = "org.apache.hadoop.ozone.recon.api";
private static final Logger LOG =
@@ -68,32 +61,18 @@ public ReconRestServletModule(ConfigurationSource conf) {
@Override
protected void configureServlets() {
- configureApi(BASE_API_PATH, API_PACKAGE);
+ configureApi(API_PACKAGE);
}
- private void configureApi(String baseApiPath, String... packages) {
+ private void configureApi(String... packages) {
StringBuilder sb = new StringBuilder();
- Set<String> adminEndpoints = new HashSet<>();
for (String pkg : packages) {
if (sb.length() > 0) {
sb.append(',');
}
- checkIfPackageExistsAndLog(pkg, baseApiPath);
+ checkIfPackageExistsAndLog(pkg);
sb.append(pkg);
- // Check for classes marked as admin only that will need an extra
- // filter applied to their path.
- Reflections reflections = new Reflections(pkg,
- new TypeAnnotationsScanner(), new SubTypesScanner());
- Set<Class<?>> adminEndpointClasses =
- reflections.getTypesAnnotatedWith(AdminOnly.class);
- adminEndpointClasses.stream()
- .map(clss -> UriBuilder.fromResource(clss).build().toString())
- .forEachOrdered(adminEndpoints::add);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Registered the following endpoint classes as admin only:
{}",
- adminEndpointClasses);
- }
}
Map<String, String> params = new HashMap<>();
params.put("javax.ws.rs.Application",
@@ -103,46 +82,36 @@ private void configureApi(String baseApiPath, String...
packages) {
}
bind(ServletContainer.class).in(Scopes.SINGLETON);
- String allApiPath =
- UriBuilder.fromPath(baseApiPath).path("*").build().toString();
+ String allApiPath =
UriBuilder.fromPath(BASE_API_PATH).path("*").build().toString();
serve(allApiPath).with(ServletContainer.class, params);
- addFilters(baseApiPath, adminEndpoints);
- }
- private void addFilters(String basePath, Set<String> adminSubPaths) {
if (OzoneSecurityUtil.isHttpSecurityEnabled(conf)) {
- String authPath =
- UriBuilder.fromPath(basePath).path("*").build().toString();
- filter(authPath).through(ReconAuthFilter.class);
+ filter(allApiPath).through(ReconAuthFilter.class);
if (LOG.isDebugEnabled()) {
- LOG.debug("Added authentication filter to path {}", authPath);
+ LOG.debug("Added authentication filter to path {}", allApiPath);
}
boolean aclEnabled = conf.getBoolean(OZONE_ACL_ENABLED,
OZONE_ACL_ENABLED_DEFAULT);
if (aclEnabled) {
- for (String path: adminSubPaths) {
- String adminPath =
- UriBuilder.fromPath(basePath).path(path +
"*").build().toString();
- filter(adminPath).through(ReconAdminFilter.class);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Added admin filter to path {}", adminPath);
- }
+ filter(allApiPath).through(ReconAdminFilter.class);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Added admin filter to path {}", allApiPath);
}
}
}
}
- private void checkIfPackageExistsAndLog(String pkg, String path) {
+ private void checkIfPackageExistsAndLog(String pkg) {
String resourcePath = pkg.replace(".", "/");
URL resource = getClass().getClassLoader().getResource(resourcePath);
if (resource != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Using API endpoints from package {} for paths under {}.",
- pkg, path);
+ pkg, BASE_API_PATH);
}
} else {
- LOG.warn("No Beans in '{}' found. Requests {} will fail.", pkg, path);
+ LOG.warn("No Beans in '{}' found. Requests {} will fail.", pkg,
BASE_API_PATH);
}
}
}
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/AccessHeatMapEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/AccessHeatMapEndpoint.java
index bea041836ec..dbddc2f3d80 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/AccessHeatMapEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/AccessHeatMapEndpoint.java
@@ -41,7 +41,6 @@
*/
@Path("/heatmap")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
@InternalOnly(feature = "Heatmap", description = "Heatmap feature has " +
"dependency on heatmap provider service component implementation.")
public class AccessHeatMapEndpoint {
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/AdminOnly.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/AdminOnly.java
deleted file mode 100644
index f5cf5011f82..00000000000
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/AdminOnly.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.hadoop.ozone.recon.api;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import javax.ws.rs.Path;
-
-/**
- * Annotation to apply to endpoint classes that also have a {@link Path}
- * annotation that will cause their access to be restricted to ozone and
- * recon administrators only.
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface AdminOnly {
-}
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BlocksEndPoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BlocksEndPoint.java
index 7bd1e74014b..0576b41873e 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BlocksEndPoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BlocksEndPoint.java
@@ -53,7 +53,6 @@
*/
@Path("/blocks")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class BlocksEndPoint {
private final DBStore scmDBStore;
private final ReconContainerManager containerManager;
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BucketEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BucketEndpoint.java
index e7ec01900b9..0eb324e41bd 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BucketEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/BucketEndpoint.java
@@ -44,7 +44,6 @@
*/
@Path("/buckets")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class BucketEndpoint {
@Inject
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 49e3c0870a4..9bb7d76ec34 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
@@ -90,7 +90,6 @@
*/
@Path("/containers")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class ContainerEndpoint {
private ReconContainerMetadataManager reconContainerMetadataManager;
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/FeaturesEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/FeaturesEndpoint.java
index 6f81abd1622..e06bbe89dc4 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/FeaturesEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/FeaturesEndpoint.java
@@ -40,7 +40,6 @@
*/
@Path("/features")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class FeaturesEndpoint {
private static final Logger LOG =
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java
index 0da84ab066b..101be8eb4f9 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java
@@ -43,7 +43,6 @@
*/
@Path("/namespace")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class NSSummaryEndpoint {
private final ReconNamespaceSummaryManager reconNamespaceSummaryManager;
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 c07d841417a..701529e8f76 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
@@ -95,7 +95,6 @@
*/
@Path("/keys")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class OMDBInsightEndpoint {
private final ReconOMMetadataManager omMetadataManager;
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/TriggerDBSyncEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/TriggerDBSyncEndpoint.java
index 4f91b01db87..94c3c3776d5 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/TriggerDBSyncEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/TriggerDBSyncEndpoint.java
@@ -30,7 +30,6 @@
*/
@Path("/triggerdbsync")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class TriggerDBSyncEndpoint {
private OzoneManagerServiceProvider ozoneManagerServiceProvider;
diff --git
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/VolumeEndpoint.java
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/VolumeEndpoint.java
index e46c85ffdfd..8df4b2b5a44 100644
---
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/VolumeEndpoint.java
+++
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/VolumeEndpoint.java
@@ -43,7 +43,6 @@
*/
@Path("/volumes")
@Produces(MediaType.APPLICATION_JSON)
-@AdminOnly
public class VolumeEndpoint {
@Inject
diff --git
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
index b6aa522d4e7..24dd171d6e6 100644
---
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
+++
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
@@ -17,91 +17,25 @@
package org.apache.hadoop.ozone.recon.api.filters;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import com.google.common.collect.Sets;
import java.security.Principal;
-import java.util.HashSet;
-import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.Path;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.recon.ReconConfigKeys;
import org.apache.hadoop.ozone.OzoneConfigKeys;
-import org.apache.hadoop.ozone.recon.api.AdminOnly;
-import org.apache.hadoop.ozone.recon.api.ClusterStateEndpoint;
-import org.apache.hadoop.ozone.recon.api.MetricsProxyEndpoint;
-import org.apache.hadoop.ozone.recon.api.NodeEndpoint;
-import org.apache.hadoop.ozone.recon.api.PipelineEndpoint;
-import org.apache.hadoop.ozone.recon.api.TaskStatusService;
-import org.apache.hadoop.ozone.recon.api.UtilizationEndpoint;
import org.apache.hadoop.security.UserGroupInformation;
import org.junit.jupiter.api.Test;
-import org.reflections.Reflections;
-import org.reflections.scanners.SubTypesScanner;
-import org.reflections.scanners.TypeAnnotationsScanner;
/**
* Tests the admin filter on recon endpoints which should only be accessible
* to admins.
*/
public class TestAdminFilter {
- /**
- * Tests that only designated endpoints are not marked with the @AdminOnly
- * annotation, meaning they are accessible to all users.
- */
- @Test
- public void testAdminOnlyEndpoints() {
- // Get all classes with @Path annotation anywhere in recon.
- Reflections reflections = new Reflections(
- "org.apache.hadoop.ozone.recon",
- new TypeAnnotationsScanner(),
- new SubTypesScanner());
- Set<Class<?>> allEndpoints =
- reflections.getTypesAnnotatedWith(Path.class);
-
- assertThat(allEndpoints).isNotEmpty();
-
- // If an endpoint is added, it must either require admin privileges by
being
- // marked with the `@AdminOnly` annotation, or be added to this set to
exclude it.
- // - Any endpoint that displays information related to the filesystem
namespace
- // (including aggregate counts), user information, or allows
modification to the
- // cluster's state should be marked as `@AdminOnly`.
- // - Read-only endpoints that only return information about node status or
- // cluster state do not require the `@AdminOnly` annotation and can be
excluded
- // from admin requirements by adding them to this set.
- Set<Class<?>> nonAdminEndpoints = new HashSet<>();
- nonAdminEndpoints.add(UtilizationEndpoint.class);
- nonAdminEndpoints.add(ClusterStateEndpoint.class);
- nonAdminEndpoints.add(MetricsProxyEndpoint.class);
- nonAdminEndpoints.add(NodeEndpoint.class);
- nonAdminEndpoints.add(PipelineEndpoint.class);
- nonAdminEndpoints.add(TaskStatusService.class);
-
- assertThat(allEndpoints).containsAll(nonAdminEndpoints);
-
- Set<Class<?>> adminEndpoints = Sets.difference(allEndpoints,
- nonAdminEndpoints);
-
- for (Class<?> endpoint: nonAdminEndpoints) {
- assertFalse(endpoint.isAnnotationPresent(AdminOnly.class),
- String.format("Endpoint class %s has been declared as non admin " +
- "in this test, but is marked as @AdminOnly.", endpoint));
- }
-
- for (Class<?> endpoint: adminEndpoints) {
- assertTrue(endpoint.isAnnotationPresent(AdminOnly.class),
- String.format("Endpoint class %s must be marked as @AdminOnly " +
- "or explicitly declared as non admin in this test.", endpoint));
- }
- }
@Test
public void testAdminFilterOzoneAdminsOnly() throws Exception {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]