REPO-2850 Heartbeat: Add support for turning the data collection on/off based on the heartbeat.enabled global property

* Add support for enabled property
* Add support for hearbeat target url propery
This commit is contained in:
Erik Knizat
2017-09-04 16:43:45 +01:00
committed by Ancuta Morarasu
parent 58f272d01b
commit fc05980f98
5 changed files with 347 additions and 328 deletions

View File

@@ -41,7 +41,15 @@ public class HBDataCollectorServiceImpl implements HBDataCollectorService
private List<HBBaseDataCollector> collectors = new LinkedList<>(); private List<HBBaseDataCollector> collectors = new LinkedList<>();
private HBDataSenderService hbDataSenderService; private HBDataSenderService hbDataSenderService;
private boolean enabled; private boolean enabled = false;
/** The default enable state */
private final boolean defaultHbState;
public HBDataCollectorServiceImpl (boolean defaultHeartBeatState)
{
this.defaultHbState = defaultHeartBeatState;
this.enabled = defaultHeartBeatState;
}
public void setHbDataSenderService(HBDataSenderService hbDataSenderService) public void setHbDataSenderService(HBDataSenderService hbDataSenderService)
{ {
@@ -72,9 +80,21 @@ public class HBDataCollectorServiceImpl implements HBDataCollectorService
} }
@Override @Override
public boolean isHBEnabled() public boolean getDefaultHbState()
{
return defaultHbState;
}
@Override
public boolean isHbEnabled()
{ {
return enabled; return enabled;
} }
@Override
public void setHbEnabled(boolean enabled)
{
this.enabled = enabled;
}
} }

View File

@@ -1,296 +1,285 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2016 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by * it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.heartbeat; package org.alfresco.heartbeat;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Date; import java.util.Date;
import org.alfresco.service.cmr.repository.HBDataCollectorService; import org.alfresco.service.cmr.repository.HBDataCollectorService;
import org.alfresco.service.license.LicenseDescriptor; import org.alfresco.service.license.LicenseDescriptor;
import org.alfresco.service.license.LicenseService; import org.alfresco.service.license.LicenseService;
import org.alfresco.service.license.LicenseService.LicenseChangeHandler; import org.alfresco.service.license.LicenseService.LicenseChangeHandler;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.quartz.Job; import org.quartz.Job;
import org.quartz.JobDataMap; import org.quartz.JobDataMap;
import org.quartz.JobDetail; import org.quartz.JobDetail;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException; import org.quartz.JobExecutionException;
import org.quartz.Scheduler; import org.quartz.Scheduler;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger; import org.quartz.SimpleTrigger;
import org.quartz.Trigger; import org.quartz.Trigger;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
/** /**
* This class communicates some very basic repository statistics to Alfresco on a regular basis. * This class communicates some very basic repository statistics to Alfresco on a regular basis.
* *
* @author dward * @author dward
*/ */
public class HeartBeat implements LicenseChangeHandler public class HeartBeat implements LicenseChangeHandler
{ {
private static final String LAMBDA_INGEST_URL = "https://0s910f9ijc.execute-api.eu-west-1.amazonaws.com/Stage/ingest"; /** The logger. */
private static final Log logger = LogFactory.getLog(HeartBeat.class);
/** The default enable state */
private static final boolean DEFAULT_HEARTBEAT_ENABLED = true; private LicenseService licenseService;
/** The logger. */ private Scheduler scheduler;
private static final Log logger = LogFactory.getLog(HeartBeat.class);
private boolean testMode = true;
private LicenseService licenseService;
private final String JOB_NAME = "heartbeat";
private Scheduler scheduler;
private HBDataCollectorService dataCollectorService;
/** URL to post heartbeat to. */
private String heartBeatUrl;
private boolean testMode = true; /**
* Initialises the heart beat service. Note that dependencies are intentionally 'pulled' rather than injected
private final String JOB_NAME = "heartbeat"; * because we don't want these to be reconfigured.
*
/** Is the heartbeat enabled */ * @param context
private boolean enabled = DEFAULT_HEARTBEAT_ENABLED; * the context
*/
private HBDataCollectorService dataCollectorService; public HeartBeat(final ApplicationContext context)
{
this(context, true);
}
/**
* Initialises the heart beat service. Note that dependencies are intentionally 'pulled' rather than injected /**
* because we don't want these to be reconfigured. * Initialises the heart beat service, potentially in test mode. Note that dependencies are intentionally 'pulled'
* * rather than injected because we don't want these to be reconfigured.
* @param context *
* the context * -@param context
*/ * the context
public HeartBeat(final ApplicationContext context) * -@param testMode
{ * are we running in test mode? If so we send data to local port 9999 rather than an alfresco server. We
this(context, true); * also use a special test encryption certificate and ping on a more frequent basis.
} */
public HeartBeat(final ApplicationContext context, final Boolean testModel)
/** {
* Initialises the heart beat service, potentially in test mode. Note that dependencies are intentionally 'pulled' logger.debug("Initialising HeartBeat");
* rather than injected because we don't want these to be reconfigured.
*
* -@param context // I think these should be wired by spring instead for proper ioc..
* the context this.dataCollectorService = (HBDataCollectorService) context.getBean("hbDataCollectorService");
* -@param testMode this.scheduler = (Scheduler) context.getBean("schedulerFactory");
* are we running in test mode? If so we send data to local port 9999 rather than an alfresco server. We
* also use a special test encryption certificate and ping on a more frequent basis. this.testMode = testModel;
*/
public HeartBeat(final ApplicationContext context, final Boolean testModel) try
{ {
logger.debug("Initialising HeartBeat"); LicenseService licenseService = null;
try
{
// I think these should be wired by spring instead for proper ioc.. licenseService = (LicenseService) context.getBean("licenseService");
this.dataCollectorService = (HBDataCollectorService) context.getBean("hbDataCollectorService"); licenseService.registerOnLicenseChange(this);
this.scheduler = (Scheduler) context.getBean("schedulerFactory"); }
catch (NoSuchBeanDefinitionException e)
this.testMode = testModel; {
logger.error("licenseService not found", e);
try }
{ this.licenseService = licenseService;
LicenseService licenseService = null;
try // We force the job to be scheduled regardless of the potential state of the licenses
{ scheduleJob();
licenseService = (LicenseService) context.getBean("licenseService"); }
licenseService.registerOnLicenseChange(this); catch (final RuntimeException e)
} {
catch (NoSuchBeanDefinitionException e) throw e;
{ }
logger.error("licenseService not found", e); catch (final Exception e)
} {
this.licenseService = licenseService; throw new RuntimeException(e);
}
// We force the job to be scheduled regardless of the potential state of the licenses }
scheduleJob();
} // private synchronized void setHeartBeatUrl(String heartBeatUrl)
catch (final RuntimeException e) // {
{ // this.heartBeatUrl = heartBeatUrl;
throw e; // }
} //
catch (final Exception e) // // Determine the URL to send the heartbeat to from the license if not set
{ // private synchronized String getHeartBeatUrl()
throw new RuntimeException(e); // {
} // if (heartBeatUrl == null)
} // {
// // GC: Ignore the standard heartbeat URL and always use the AWS/Lambda URL
private synchronized void setHeartBeatUrl(String heartBeatUrl) //// LicenseDescriptor licenseDescriptor = licenseService.getLicense();
{ //// String url = (licenseDescriptor == null) ? null : licenseDescriptor.getHeartBeatUrl();
this.heartBeatUrl = heartBeatUrl; //// setHeartBeatUrl(url == null ? HeartBeat.DEFAULT_URL : url);
} // setHeartBeatUrl(LAMBDA_INGEST_URL);
// }
// Determine the URL to send the heartbeat to from the license if not set //
private synchronized String getHeartBeatUrl() // logger.debug("Returning heartBeatUrl: " + heartBeatUrl);
{ //
if (heartBeatUrl == null) // return heartBeatUrl;
{ // }
// GC: Ignore the standard heartbeat URL and always use the AWS/Lambda URL
// LicenseDescriptor licenseDescriptor = licenseService.getLicense(); /**
// String url = (licenseDescriptor == null) ? null : licenseDescriptor.getHeartBeatUrl(); * @return <tt>true</tt> if the heartbeat is currently enabled
// setHeartBeatUrl(url == null ? HeartBeat.DEFAULT_URL : url); */
setHeartBeatUrl(LAMBDA_INGEST_URL); public synchronized boolean isEnabled()
} {
return dataCollectorService.isHbEnabled();
logger.debug("Returning heartBeatUrl: " + heartBeatUrl); }
return heartBeatUrl;
}
/**
/** * Sends encrypted data over HTTP.
* @return <tt>true</tt> if the heartbeat is currently enabled *
*/ * @throws IOException
public synchronized boolean isEnabled() * Signals that an I/O exception has occurred.
{ * @throws GeneralSecurityException
return enabled; * an encryption related exception
} */
public void collectAndSendData() throws IOException, GeneralSecurityException
{
this.dataCollectorService.collectAndSendData();
/** }
* Sends encrypted data over HTTP.
* /**
* @throws IOException * Listens for license changes. If a license is change or removed, the heartbeat job is rescheduled.
* Signals that an I/O exception has occurred. */
* @throws GeneralSecurityException public synchronized void onLicenseChange(LicenseDescriptor licenseDescriptor)
* an encryption related exception {
*/ logger.debug("Update license called");
public void collectAndSendData() throws IOException, GeneralSecurityException
{ //setHeartBeatUrl(licenseDescriptor.getHeartBeatUrl());
this.dataCollectorService.collectAndSendData(); boolean newEnabled = !licenseDescriptor.isHeartBeatDisabled();
}
if (newEnabled != dataCollectorService.isHbEnabled())
/** {
* Listens for license changes. If a license is change or removed, the heartbeat job is rescheduled. logger.debug("State change of heartbeat");
*/ dataCollectorService.setHbEnabled(newEnabled);
public synchronized void onLicenseChange(LicenseDescriptor licenseDescriptor) try
{ {
logger.debug("Update license called"); scheduleJob();
}
setHeartBeatUrl(licenseDescriptor.getHeartBeatUrl()); catch (Exception e)
boolean newEnabled = !licenseDescriptor.isHeartBeatDisabled(); {
logger.error("Unable to schedule heart beat", e);
if (newEnabled != enabled) }
{ }
logger.debug("State change of heartbeat"); }
this.enabled = newEnabled;
try /**
{ * License load failure resets the heartbeat back to the default state
scheduleJob(); */
} @Override
catch (Exception e) public synchronized void onLicenseFail()
{ {
logger.error("Unable to schedule heart beat", e); boolean newEnabled = dataCollectorService.getDefaultHbState();
}
} if (newEnabled != dataCollectorService.isHbEnabled())
} {
logger.debug("State change of heartbeat");
/** dataCollectorService.setHbEnabled(newEnabled);
* License load failure resets the heartbeat back to the default state try
*/ {
@Override scheduleJob();
public synchronized void onLicenseFail() }
{ catch (Exception e)
boolean newEnabled = DEFAULT_HEARTBEAT_ENABLED; {
logger.error("Unable to schedule heart beat", e);
if (newEnabled != enabled) }
{ }
logger.debug("State change of heartbeat"); }
this.enabled = newEnabled;
try /**
{ * Start or stop the hertbeat job depending on whether the heartbeat is enabled or not
scheduleJob(); * @throws SchedulerException
} */
catch (Exception e) private synchronized void scheduleJob() throws SchedulerException
{ {
logger.error("Unable to schedule heart beat", e); // Schedule the heart beat to run regularly
} if(dataCollectorService.isHbEnabled())
} {
} logger.debug("heartbeat job scheduled");
final JobDetail jobDetail = new JobDetail(JOB_NAME, Scheduler.DEFAULT_GROUP, HeartBeatJob.class);
/** jobDetail.getJobDataMap().put("heartBeat", this);
* Start or stop the hertbeat job depending on whether the heartbeat is enabled or not // Ensure the job wasn't already scheduled in an earlier retry of this transaction
* @throws SchedulerException final String triggerName = JOB_NAME + "Trigger";
*/ scheduler.unscheduleJob(triggerName, Scheduler.DEFAULT_GROUP);
private synchronized void scheduleJob() throws SchedulerException final Trigger trigger = new SimpleTrigger(triggerName, Scheduler.DEFAULT_GROUP, new Date(), null,
{ //SimpleTrigger.REPEAT_INDEFINITELY, testMode ? 1000 : 4 * 60 * 60 * 1000);
// Schedule the heart beat to run regularly SimpleTrigger.REPEAT_INDEFINITELY, testMode ? 1000 : 2 * 60 * 1000);
if(enabled) scheduler.scheduleJob(jobDetail, trigger);
{ }
logger.debug("heartbeat job scheduled"); else
final JobDetail jobDetail = new JobDetail(JOB_NAME, Scheduler.DEFAULT_GROUP, HeartBeatJob.class); {
jobDetail.getJobDataMap().put("heartBeat", this); logger.debug("heartbeat job unscheduled");
// Ensure the job wasn't already scheduled in an earlier retry of this transaction final String triggerName = JOB_NAME + "Trigger";
final String triggerName = JOB_NAME + "Trigger"; scheduler.unscheduleJob(triggerName, Scheduler.DEFAULT_GROUP);
scheduler.unscheduleJob(triggerName, Scheduler.DEFAULT_GROUP); }
final Trigger trigger = new SimpleTrigger(triggerName, Scheduler.DEFAULT_GROUP, new Date(), null, }
//SimpleTrigger.REPEAT_INDEFINITELY, testMode ? 1000 : 4 * 60 * 60 * 1000);
SimpleTrigger.REPEAT_INDEFINITELY, testMode ? 1000 : 2 * 60 * 1000); /**
scheduler.scheduleJob(jobDetail, trigger); * The scheduler job responsible for triggering a heartbeat on a regular basis.
} */
else public static class HeartBeatJob implements Job
{ {
logger.debug("heartbeat job unscheduled"); public void execute(final JobExecutionContext jobexecutioncontext) throws JobExecutionException
final String triggerName = JOB_NAME + "Trigger"; {
scheduler.unscheduleJob(triggerName, Scheduler.DEFAULT_GROUP); final JobDataMap dataMap = jobexecutioncontext.getJobDetail().getJobDataMap();
} final HeartBeat heartBeat = (HeartBeat) dataMap.get("heartBeat");
} try
{
/** heartBeat.collectAndSendData();
* The scheduler job responsible for triggering a heartbeat on a regular basis. }
*/ catch (final Exception e)
public static class HeartBeatJob implements Job {
{ if (logger.isDebugEnabled())
public void execute(final JobExecutionContext jobexecutioncontext) throws JobExecutionException {
{ // Verbose logging
final JobDataMap dataMap = jobexecutioncontext.getJobDetail().getJobDataMap(); HeartBeat.logger.debug("Heartbeat job failure", e);
final HeartBeat heartBeat = (HeartBeat) dataMap.get("heartBeat"); }
try else
{ {
heartBeat.collectAndSendData(); // Heartbeat errors are non-fatal and will show as single line warnings
} HeartBeat.logger.warn(e.toString());
catch (final Exception e) throw new JobExecutionException(e);
{ }
if (logger.isDebugEnabled()) }
{ }
// Verbose logging }
HeartBeat.logger.debug("Heartbeat job failure", e);
}
else }
{
// Heartbeat errors are non-fatal and will show as single line warnings
HeartBeat.logger.warn(e.toString());
throw new JobExecutionException(e);
}
}
}
}
}

View File

@@ -33,7 +33,11 @@ public interface HBDataCollectorService
void collectAndSendData(); void collectAndSendData();
boolean isHBEnabled(); boolean isHbEnabled();
void setHbEnabled(boolean enabled);
boolean getDefaultHbState();
} }

View File

@@ -1,29 +1,31 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans> <beans>
<bean id="hbDataSenderService" class="org.alfresco.heartbeat.datasender.internal.HBDataSenderServiceImpl"> <bean id="hbDataSenderService" class="org.alfresco.heartbeat.datasender.internal.HBDataSenderServiceImpl">
</bean> <constructor-arg value="${heartbeat.target.url}" />
</bean>
<!-- HeartBeat data collector service -->
<bean id="hbDataCollectorService" class="org.alfresco.heartbeat.HBDataCollectorServiceImpl"> <!-- HeartBeat data collector service -->
<property name="hbDataSenderService" ref="hbDataSenderService" /> <bean id="hbDataCollectorService" class="org.alfresco.heartbeat.HBDataCollectorServiceImpl">
</bean> <constructor-arg value="${heartbeat.enabled}" />
<property name="hbDataSenderService" ref="hbDataSenderService" />
<!-- HeartBeat abstract data collector --> </bean>
<bean id="hbBaseDataCollector" class="org.alfresco.heartbeat.HBBaseDataCollector" abstract="true">
<property name="hbDataCollectorService" ref="hbDataCollectorService"/> <!-- HeartBeat abstract data collector -->
</bean> <bean id="hbBaseDataCollector" class="org.alfresco.heartbeat.HBBaseDataCollector" abstract="true">
<property name="hbDataCollectorService" ref="hbDataCollectorService"/>
<!-- HeartBeat community data collector --> </bean>
<bean id="hbCommunityDataCollector" class="org.alfresco.heartbeat.CommunityHBDataCollector" parent="hbBaseDataCollector" init-method="register">
<property name="currentRepoDescriptorDAO" ref="currentRepoDescriptorDAO"/> <!-- HeartBeat community data collector -->
<property name="serverDescriptorDAO" ref="serverDescriptorDAO"/> <bean id="hbCommunityDataCollector" class="org.alfresco.heartbeat.CommunityHBDataCollector" parent="hbBaseDataCollector" init-method="register">
<property name="authorityService" ref="authorityService"/> <property name="currentRepoDescriptorDAO" ref="currentRepoDescriptorDAO"/>
<property name="repoUsageComponent" ref="repoUsageComponent"/> <property name="serverDescriptorDAO" ref="serverDescriptorDAO"/>
<property name="transactionService" ref="transactionService"/> <property name="authorityService" ref="authorityService"/>
<property name="customModelService" ref="customModelService"/> <property name="repoUsageComponent" ref="repoUsageComponent"/>
</bean> <property name="transactionService" ref="transactionService"/>
<property name="customModelService" ref="customModelService"/>
</beans> </bean>
</beans>

View File

@@ -1242,3 +1242,7 @@ system.email.sender.default=noreply@alfresco.com
# reset password workflow will expire in an hour # reset password workflow will expire in an hour
system.reset-password.endTimer=PT1H system.reset-password.endTimer=PT1H
system.reset-password.sendEmailAsynchronously=true system.reset-password.sendEmailAsynchronously=true
# HeartBeat
heartbeat.enabled=false
heartbeat.target.url=heartbeat.alfresco.com