mgao0 commented on code in PR #2149:
URL: https://github.com/apache/helix/pull/2149#discussion_r900417134
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java:
##########
@@ -19,51 +19,102 @@
* under the License.
*/
+import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixManager;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* A default callback implementation class to be used in {@link
HelixCloudEventListener}
*/
public class DefaultCloudEventCallbackImpl {
+ private static final Logger LOG =
LoggerFactory.getLogger(DefaultCloudEventCallbackImpl.class);
+ private final String _instanceReason = "Cloud event in
DefaultCloudEventCallback at %s";
+ private final String _emmReason = "Cloud event EMM in
DefaultCloudEventCallback by %s at %s";
/**
- * Disable the instance
+ * Disable the instance and track the cloud event in map field
disabledInstancesWithInfo in
+ * cluster config. Will not re-disable the instance if the instance is
already disabled for
+ * other reason. (So we will not overwrite the disabled reason and enable
this instance when
+ * on-unpause)
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void disableInstance(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ String message = String.format(_instanceReason,
System.currentTimeMillis());
+ LOG.info("DefaultCloudEventCallbackImpl disabled Instance {}",
manager.getInstanceName());
+ if (InstanceValidationUtil
+ .isEnabled(manager.getHelixDataAccessor(), manager.getInstanceName()))
{
+ manager.getClusterManagmentTool()
+ .enableInstance(manager.getClusterName(), manager.getInstanceName(),
false,
+ InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message);
+ }
+
HelixEventHandlingUtil.updateCloudEventOperationInClusterConfig(manager.getClusterName(),
+ manager.getInstanceName(),
manager.getHelixDataAccessor().getBaseDataAccessor(), false,
+ message);
}
/**
* Enable the instance
+ * We only enable instance that is disabled because of cloud event.
Review Comment:
Nit: Let's add one sentence such as "CloudEvent recorded in cluster config
will always (regardless the instance disable reason) be cleaned up."
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java:
##########
@@ -19,51 +19,102 @@
* under the License.
*/
+import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixManager;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* A default callback implementation class to be used in {@link
HelixCloudEventListener}
*/
public class DefaultCloudEventCallbackImpl {
+ private static final Logger LOG =
LoggerFactory.getLogger(DefaultCloudEventCallbackImpl.class);
+ private final String _instanceReason = "Cloud event in
DefaultCloudEventCallback at %s";
+ private final String _emmReason = "Cloud event EMM in
DefaultCloudEventCallback by %s at %s";
/**
- * Disable the instance
+ * Disable the instance and track the cloud event in map field
disabledInstancesWithInfo in
+ * cluster config. Will not re-disable the instance if the instance is
already disabled for
+ * other reason. (So we will not overwrite the disabled reason and enable
this instance when
+ * on-unpause)
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void disableInstance(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ String message = String.format(_instanceReason,
System.currentTimeMillis());
+ LOG.info("DefaultCloudEventCallbackImpl disabled Instance {}",
manager.getInstanceName());
+ if (InstanceValidationUtil
+ .isEnabled(manager.getHelixDataAccessor(), manager.getInstanceName()))
{
+ manager.getClusterManagmentTool()
+ .enableInstance(manager.getClusterName(), manager.getInstanceName(),
false,
+ InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message);
+ }
Review Comment:
Nit: Let's add one more logging here indicating disable complets.
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java:
##########
@@ -19,51 +19,102 @@
* under the License.
*/
+import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixManager;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* A default callback implementation class to be used in {@link
HelixCloudEventListener}
*/
public class DefaultCloudEventCallbackImpl {
+ private static final Logger LOG =
LoggerFactory.getLogger(DefaultCloudEventCallbackImpl.class);
+ private final String _instanceReason = "Cloud event in
DefaultCloudEventCallback at %s";
+ private final String _emmReason = "Cloud event EMM in
DefaultCloudEventCallback by %s at %s";
/**
- * Disable the instance
+ * Disable the instance and track the cloud event in map field
disabledInstancesWithInfo in
+ * cluster config. Will not re-disable the instance if the instance is
already disabled for
+ * other reason. (So we will not overwrite the disabled reason and enable
this instance when
+ * on-unpause)
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void disableInstance(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ String message = String.format(_instanceReason,
System.currentTimeMillis());
+ LOG.info("DefaultCloudEventCallbackImpl disabled Instance {}",
manager.getInstanceName());
+ if (InstanceValidationUtil
+ .isEnabled(manager.getHelixDataAccessor(), manager.getInstanceName()))
{
+ manager.getClusterManagmentTool()
+ .enableInstance(manager.getClusterName(), manager.getInstanceName(),
false,
+ InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message);
+ }
+
HelixEventHandlingUtil.updateCloudEventOperationInClusterConfig(manager.getClusterName(),
+ manager.getInstanceName(),
manager.getHelixDataAccessor().getBaseDataAccessor(), false,
+ message);
}
/**
* Enable the instance
+ * We only enable instance that is disabled because of cloud event.
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void enableInstance(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ LOG.info("DefaultCloudEventCallbackImpl enable Instance {}",
manager.getInstanceName());
+ String instanceName = manager.getInstanceName();
+ HelixDataAccessor accessor = manager.getHelixDataAccessor();
+ String message = String.format(_instanceReason,
System.currentTimeMillis());
+ HelixEventHandlingUtil
+ .updateCloudEventOperationInClusterConfig(manager.getClusterName(),
instanceName,
+ manager.getHelixDataAccessor().getBaseDataAccessor(), true,
message);
+ if (HelixEventHandlingUtil.isInstanceDisabledForCloudEvent(instanceName,
accessor)) {
+
manager.getClusterManagmentTool().enableInstance(manager.getClusterName(),
instanceName, true,
+ InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message);
+ }
}
/**
- *
+ * Will enter MM when the cluster is not in MM
+ * TODO: we should add maintenance reason when EMM with cloud event
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void enterMaintenanceMode(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ if
(!manager.getClusterManagmentTool().isInMaintenanceMode(manager.getClusterName()))
{
+ LOG.info("DefaultCloudEventCallbackImpl enterMaintenanceMode by {}",
+ manager.getInstanceName());
+ manager.getClusterManagmentTool()
+ .manuallyEnableMaintenanceMode(manager.getClusterName(), true,
+ String.format(_emmReason, manager.getInstanceName(),
System.currentTimeMillis()),
+ null);
+ }
}
/**
- *
+ * Will exit MM when when cluster config tracks no ongoing cloud event being
handling
+ * TODO: we should also check the maintenance reason and only exit when EMM
is caused by cloud event
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void exitMaintenanceMode(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ ClusterConfig clusterConfig = manager.getHelixDataAccessor()
+
.getProperty(manager.getHelixDataAccessor().keyBuilder().clusterConfig());
+ if (HelixEventHandlingUtil.checkNoInstanceUnderCloudEvent(clusterConfig)) {
+ LOG.info("DefaultCloudEventCallbackImpl exitMaintenanceMode by {}",
+ manager.getInstanceName());
+ manager.getClusterManagmentTool()
+ .manuallyEnableMaintenanceMode(manager.getClusterName(), false,
+ String.format(_emmReason, manager.getInstanceName(),
System.currentTimeMillis()),
+ null);
+ } else {
+ LOG.info(
Review Comment:
Nit: not sure if this will cause too large log size. Let's think about it
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java:
##########
@@ -19,51 +19,102 @@
* under the License.
*/
+import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixManager;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* A default callback implementation class to be used in {@link
HelixCloudEventListener}
*/
public class DefaultCloudEventCallbackImpl {
+ private static final Logger LOG =
LoggerFactory.getLogger(DefaultCloudEventCallbackImpl.class);
+ private final String _instanceReason = "Cloud event in
DefaultCloudEventCallback at %s";
+ private final String _emmReason = "Cloud event EMM in
DefaultCloudEventCallback by %s at %s";
/**
- * Disable the instance
+ * Disable the instance and track the cloud event in map field
disabledInstancesWithInfo in
+ * cluster config. Will not re-disable the instance if the instance is
already disabled for
+ * other reason. (So we will not overwrite the disabled reason and enable
this instance when
+ * on-unpause)
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void disableInstance(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ String message = String.format(_instanceReason,
System.currentTimeMillis());
+ LOG.info("DefaultCloudEventCallbackImpl disabled Instance {}",
manager.getInstanceName());
+ if (InstanceValidationUtil
+ .isEnabled(manager.getHelixDataAccessor(), manager.getInstanceName()))
{
+ manager.getClusterManagmentTool()
+ .enableInstance(manager.getClusterName(), manager.getInstanceName(),
false,
+ InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message);
+ }
+
HelixEventHandlingUtil.updateCloudEventOperationInClusterConfig(manager.getClusterName(),
Review Comment:
Nit: Let's put a TODO here saying that these two steps should be
transactional if we ever have the transactional support available
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/HelixEventHandlingUtil.java:
##########
@@ -19,40 +19,103 @@
* under the License.
*/
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.helix.AccessOption;
import org.apache.helix.BaseDataAccessor;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixException;
+import org.apache.helix.PropertyPathBuilder;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.manager.zk.ZKHelixAdmin;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.InstanceConfig;
+import org.apache.helix.util.ConfigStringUtil;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.zkclient.DataUpdater;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
- class HelixEventHandlingUtil {
+class HelixEventHandlingUtil {
+ private static Logger LOG =
LoggerFactory.getLogger(HelixEventHandlingUtil.class);
/**
- * Enable or disable an instance for cloud event.
- * It will enable/disable Helix for that instance. Also add the instance
cloud event info to
- * clusterConfig Znode when enable.
- * @param clusterName
+ * check if instance is disabled by cloud event.
* @param instanceName
- * @param message
- * @param isEnable
* @param dataAccessor
- * @return return failure when either enable/disable failed or update
cluster ZNode failed.
+ * @return return true only when instance is Helix disabled and the disabled
reason in
+ * instanceConfig is cloudEvent
*/
- static boolean enableInstanceForCloudEvent(String clusterName, String
instanceName, String message,
- boolean isEnable, BaseDataAccessor dataAccessor) {
- // TODO add impl here
- return true;
+ static boolean isInstanceDisabledForCloudEvent(String instanceName,
+ HelixDataAccessor dataAccessor) {
+ InstanceConfig instanceConfig =
+
dataAccessor.getProperty(dataAccessor.keyBuilder().instanceConfig(instanceName));
+ return !InstanceValidationUtil.isEnabled(dataAccessor, instanceName) &&
instanceConfig
+ .getInstanceDisabledType()
+ .equals(InstanceConstants.InstanceDisabledType.CLOUD_EVENT.name());
}
/**
- * check if instance is disabled by cloud event.
- * @param clusterName
- * @param instanceName
- * @param dataAccessor
- * @return return true only when instance is Helix disabled and has the
cloud event info in
- * clusterConfig ZNode.
+ * Update map field disabledInstancesWithInfo in clusterConfig with
cloudEvent instance info
*/
- static boolean IsInstanceDisabledForCloudEvent(String clusterName, String
instanceName,
- BaseDataAccessor dataAccessor) {
- // TODO add impl here
- return true;
+ static void updateCloudEventOperationInClusterConfig(String clusterName,
String instanceName,
Review Comment:
Nit: Could we simplify this method signature to have HelixManager passed,
and get info such as clusterName, instanceName, and baseAccessor from the
HelixManager?
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java:
##########
@@ -19,51 +19,102 @@
* under the License.
*/
+import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixManager;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* A default callback implementation class to be used in {@link
HelixCloudEventListener}
*/
public class DefaultCloudEventCallbackImpl {
+ private static final Logger LOG =
LoggerFactory.getLogger(DefaultCloudEventCallbackImpl.class);
+ private final String _instanceReason = "Cloud event in
DefaultCloudEventCallback at %s";
+ private final String _emmReason = "Cloud event EMM in
DefaultCloudEventCallback by %s at %s";
/**
- * Disable the instance
+ * Disable the instance and track the cloud event in map field
disabledInstancesWithInfo in
+ * cluster config. Will not re-disable the instance if the instance is
already disabled for
+ * other reason. (So we will not overwrite the disabled reason and enable
this instance when
+ * on-unpause)
* @param manager The helix manager associated with the listener
* @param eventInfo Detailed information about the event
*/
public void disableInstance(HelixManager manager, Object eventInfo) {
- // To be implemented
- throw new NotImplementedException();
+ String message = String.format(_instanceReason,
System.currentTimeMillis());
+ LOG.info("DefaultCloudEventCallbackImpl disabled Instance {}",
manager.getInstanceName());
Review Comment:
Nit: "disabled" is not appropriate here since the instance is not disabled
yet
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/HelixEventHandlingUtil.java:
##########
@@ -19,40 +19,103 @@
* under the License.
*/
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.helix.AccessOption;
import org.apache.helix.BaseDataAccessor;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixException;
+import org.apache.helix.PropertyPathBuilder;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.manager.zk.ZKHelixAdmin;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.InstanceConfig;
+import org.apache.helix.util.ConfigStringUtil;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.zkclient.DataUpdater;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
- class HelixEventHandlingUtil {
+class HelixEventHandlingUtil {
+ private static Logger LOG =
LoggerFactory.getLogger(HelixEventHandlingUtil.class);
/**
- * Enable or disable an instance for cloud event.
- * It will enable/disable Helix for that instance. Also add the instance
cloud event info to
- * clusterConfig Znode when enable.
- * @param clusterName
+ * check if instance is disabled by cloud event.
* @param instanceName
- * @param message
- * @param isEnable
* @param dataAccessor
- * @return return failure when either enable/disable failed or update
cluster ZNode failed.
+ * @return return true only when instance is Helix disabled and the disabled
reason in
+ * instanceConfig is cloudEvent
*/
- static boolean enableInstanceForCloudEvent(String clusterName, String
instanceName, String message,
- boolean isEnable, BaseDataAccessor dataAccessor) {
- // TODO add impl here
- return true;
+ static boolean isInstanceDisabledForCloudEvent(String instanceName,
+ HelixDataAccessor dataAccessor) {
+ InstanceConfig instanceConfig =
+
dataAccessor.getProperty(dataAccessor.keyBuilder().instanceConfig(instanceName));
+ return !InstanceValidationUtil.isEnabled(dataAccessor, instanceName) &&
instanceConfig
+ .getInstanceDisabledType()
Review Comment:
Should we do a null check for instanceConfig?
##########
helix-core/src/main/java/org/apache/helix/cloud/event/helix/HelixEventHandlingUtil.java:
##########
@@ -19,40 +19,103 @@
* under the License.
*/
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.helix.AccessOption;
import org.apache.helix.BaseDataAccessor;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixException;
+import org.apache.helix.PropertyPathBuilder;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.manager.zk.ZKHelixAdmin;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.InstanceConfig;
+import org.apache.helix.util.ConfigStringUtil;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.zkclient.DataUpdater;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
- class HelixEventHandlingUtil {
+class HelixEventHandlingUtil {
+ private static Logger LOG =
LoggerFactory.getLogger(HelixEventHandlingUtil.class);
/**
- * Enable or disable an instance for cloud event.
- * It will enable/disable Helix for that instance. Also add the instance
cloud event info to
- * clusterConfig Znode when enable.
- * @param clusterName
+ * check if instance is disabled by cloud event.
* @param instanceName
- * @param message
- * @param isEnable
* @param dataAccessor
- * @return return failure when either enable/disable failed or update
cluster ZNode failed.
+ * @return return true only when instance is Helix disabled and the disabled
reason in
+ * instanceConfig is cloudEvent
*/
- static boolean enableInstanceForCloudEvent(String clusterName, String
instanceName, String message,
- boolean isEnable, BaseDataAccessor dataAccessor) {
- // TODO add impl here
- return true;
+ static boolean isInstanceDisabledForCloudEvent(String instanceName,
+ HelixDataAccessor dataAccessor) {
+ InstanceConfig instanceConfig =
+
dataAccessor.getProperty(dataAccessor.keyBuilder().instanceConfig(instanceName));
+ return !InstanceValidationUtil.isEnabled(dataAccessor, instanceName) &&
instanceConfig
+ .getInstanceDisabledType()
+ .equals(InstanceConstants.InstanceDisabledType.CLOUD_EVENT.name());
}
/**
- * check if instance is disabled by cloud event.
- * @param clusterName
- * @param instanceName
- * @param dataAccessor
- * @return return true only when instance is Helix disabled and has the
cloud event info in
- * clusterConfig ZNode.
+ * Update map field disabledInstancesWithInfo in clusterConfig with
cloudEvent instance info
*/
- static boolean IsInstanceDisabledForCloudEvent(String clusterName, String
instanceName,
- BaseDataAccessor dataAccessor) {
- // TODO add impl here
- return true;
+ static void updateCloudEventOperationInClusterConfig(String clusterName,
String instanceName,
+ BaseDataAccessor baseAccessor, boolean enable, String message) {
+ String path = PropertyPathBuilder.clusterConfig(clusterName);
+
+ if (!baseAccessor.exists(path, 0)) {
+ throw new HelixException("Cluster " + clusterName + ": cluster config
does not exist");
+ }
+
+ if (!baseAccessor.update(path, new DataUpdater<ZNRecord>() {
+ @Override
+ public ZNRecord update(ZNRecord currentData) {
+ if (currentData == null) {
+ throw new HelixException("Cluster: " + clusterName + ": cluster
config is null");
+ }
+
+ ClusterConfig clusterConfig = new ClusterConfig(currentData);
+ Map<String, String> disabledInstancesWithInfo =
+ new TreeMap<>(clusterConfig.getDisabledInstancesWithInfo());
+ if (enable) {
+ disabledInstancesWithInfo.keySet().remove(instanceName);
+ } else {
+ // disabledInstancesWithInfo is only used for cloud event handling.
+ String timeStamp = String.valueOf(System.currentTimeMillis());
+ disabledInstancesWithInfo.put(instanceName, ZKHelixAdmin
+ .assembleInstanceBatchedDisabledInfo(
+ InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message,
timeStamp));
+ }
+ clusterConfig.setDisabledInstancesWithInfo(disabledInstancesWithInfo);
+
+ return clusterConfig.getRecord();
+ }
+ }, AccessOption.PERSISTENT)) {
+ LOG.error("Failed to update cluster config {} for {} instance {}. {}",
clusterName,
+ enable ? "enable" : "disable", instanceName, message);
+ }
}
+ /**
+ * Return true if no instance is under cloud event handling
+ * @param clusterConfig
+ * @return
+ */
+ static boolean checkNoInstanceUnderCloudEvent(ClusterConfig clusterConfig) {
+ Map<String, String> clusterConfigTrackedEvent =
clusterConfig.getDisabledInstancesWithInfo();
Review Comment:
Null check on clusterConfig?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]