[Bitbucket] How to use atlassian-scheduler in bitbucket?

Hi,

I am developing a bitbucket plugin which needs to run some automated tasks. So I first tried SAL scheduler. But then I found out that bitbucket server does not support SAL scheduler because of a known bug mentioned in here.

https://jira.atlassian.com/browse/BSERV-8556

and in that bug, it has recommended to use atlassian-scheduler instead of SAL scheduler. But the problem is I could not find a simple example which used this atlassian-scheduler ( https://bitbucket.org/atlassian/atlassian-scheduler ) other than from here

https://bitbucket.org/cfuller/atlassian-scheduler-jira-example

this example is an jira one. The problem is this example is a bit complex to me and I would really apprciate if anyone can give me a simple sample code to automate a simple task.

Thanks

1 answer

1 accepted

0 vote
Chris Fuller Atlassian Team May 16, 2017

Hi.  I'm the original author of the atlassian-scheduler API and the scheduler example you cited in your question.  That example grew to be a bit more than I had originally intended, as we also ended up adding a few things to it for use in testing and proving that it could work with things like AO that needed to access other components after JIRA was initialized.

As far as the Atlassian Scheduler API itself is concerned, however, there are really only two things that you absolutely must deal with:

  1. You need to create a JobRunner and register it with the SchedulerService when your plugin initializes.  This works very much like registering an event listener does.  The JobRunner can be an ordinary component with direct injection or constructed explicitly by some other component; the details do not matter, as long as it is a concrete singleton object that you register with the scheduler service (and that you also unregister it if your plugin is uninstalled).
  2. You need to create a schedule for instances of the job and schedule it.  If your job is a RUN_LOCALLY job, this needs to happen every time your plugin starts because it will not be persisted across restarts.  If your job is a RUN_ONCE_PER_CLUSTER job, then it will persist and you do not need to recreate it every time (you can if you want, but note that for interval jobs this will generally mean that it runs sooner than it otherwise would have).

I'm unlikely to have any spare time to simplify the example in the immediate future, but you can probably see the earlier versions of it just by looking back in the repo's history to before all the Active Objects, debugging, and SAL extras were added.  For example, try checking it out at 4d2b84d.

I don't work on Bitbucket, so I'm not as familiar with their code base, but there shouldn't be much about the example that is JIRA-specific.  If you have more specific questions, feel free to ask.

 

Hi Chris,

Thank you for the quick reply. I managed to create an small exmple from your help.

I need to clarify following 2 qs and would apprciate if you can help me on this.

1. Why there is 2 identifiers called JobRunnerKey and JobId? What is the difference?

2. I need 2 schedulers where 1st scheduler print "Job A" for each 6000ms and 2nd scheduler print "Job B" foreach 12000ms.

My original code was this. (1 scheduler, 1 job)

public class PluginJobRunnerImpl implements PluginJobRunner, InitializingBean, DisposableBean
{
    private static final Random RANDOM = new Random();
    private static final long SCHEDULE_BASE_INTERVAL_MILLIS = 6000L;
    private static final int SCHEDULE_MAX_JITTER = 10000;

    private static final Logger LOG = LoggerFactory.getLogger(PluginJobRunnerImpl.class);
    private static final JobRunnerKey JOB = JobRunnerKey.of(PluginJobRunnerImpl.class.getName());
    private static final String JOB_ID_PREFIX = "Job ID =";

    private final SchedulerService schedulerService;


    public PluginJobRunnerImpl(SchedulerService schedulerService)
    {
        this.schedulerService = schedulerService;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        schedulerService.registerJobRunner(JOB, this);
        createSchedule(1, SCHEDULE_BASE_INTERVAL_MILLIS);
    }

    @Override
    public void destroy()
    {
        schedulerService.unregisterJobRunner(JOB);
    }

    @Override
    public JobRunnerResponse runJob(JobRunnerRequest request)
    {
        try
        {
            System.out.println("Job A");
   return JobRunnerResponse.success();
        }
        catch (Exception e)
        {
            return JobRunnerResponse.failed(e);
        }
    }

    @Override
    public void createSchedule(int jobID, long intervalInMillis) throws Exception
    {
        final int jitter = RANDOM.nextInt(SCHEDULE_MAX_JITTER);
        final Date firstRun = new Date(System.currentTimeMillis() + jitter);
        final JobConfig jobConfig = JobConfig.forJobRunnerKey(JOB)
                .withSchedule(Schedule.forInterval(intervalInMillis, firstRun))
                .withRunMode(RunMode.RUN_ONCE_PER_CLUSTER);
        try
        {
            final JobId jobId = toJobId(jobID);
            final JobDetails existing = schedulerService.getJobDetails(jobId);
            if (existing != null)
            {
                schedulerService.unscheduleJob(jobId);
            }
            schedulerService.scheduleJob(jobId, jobConfig);
        }
        catch (SchedulerServiceException sse)
        {
            throw new Exception("Unable to create schedule for job ID '" + jobID + '\'', sse);
        }
    }

    @Override
    public void deleteSchedule(@Nonnull String jobId) throws Exception
    {
        final JobId id = JobId.of(jobId);
        final JobDetails jobDetails = schedulerService.getJobDetails(id);
        if (jobDetails != null)
        {
            if (!JOB.equals(jobDetails.getJobRunnerKey()))
            {
                throw new Exception("JobId '" + jobId + "' does not belong to me!");
            }
            schedulerService.unscheduleJob(id);
        }
    }

    private static JobId toJobId(int jobID)
    {
        return JobId.of(JOB_ID_PREFIX + jobID);
    }



}

And I did some modifications to it in oder to create 2 schedulers. Is there any other way than this? 

public class PluginJobRunnerImpl2 implements InitializingBean, DisposableBean
{
    private static final Random RANDOM = new Random();
    private static final long SCHEDULE_BASE_INTERVAL_MILLIS_FOR_JOB_A = 6000L;
    private static final long SCHEDULE_BASE_INTERVAL_MILLIS_FOR_JOB_B = 12000L;
    private static final int SCHEDULE_MAX_JITTER = 10000;

    private static final Logger LOG = LoggerFactory.getLogger(PluginJobRunnerImpl2.class);
    private static final JobRunnerKey JOB_A = JobRunnerKey.of(JobRunnerA.class.getName());
    private static final JobRunnerKey JOB_B = JobRunnerKey.of(JobRunnerB.class.getName());
    private static final String JOB_ID_PREFIX = "Job ID =";

    private final SchedulerService schedulerService;


    public PluginJobRunnerImpl2(SchedulerService schedulerService)
    {
        this.schedulerService = schedulerService;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        schedulerService.registerJobRunner(JOB_A, new JobRunnerA());
        schedulerService.registerJobRunner(JOB_B, new JobRunnerB());

        createSchedule(JOB_A, 1, SCHEDULE_BASE_INTERVAL_MILLIS_FOR_JOB_A); //1 is just an unique ID for job id
        createSchedule(JOB_B, 2, SCHEDULE_BASE_INTERVAL_MILLIS_FOR_JOB_B); //2 is just an unique ID for job id
    }

    @Override
    public void destroy()
    {
        schedulerService.unregisterJobRunner(JOB_A);
        schedulerService.unregisterJobRunner(JOB_B);
    }


    public void createSchedule(JobRunnerKey JOB, int jobID, long intervalInMillis) throws Exception
    {
        final int jitter = RANDOM.nextInt(SCHEDULE_MAX_JITTER);
        final Date firstRun = new Date(System.currentTimeMillis() + jitter);
        final JobConfig jobConfig = JobConfig.forJobRunnerKey(JOB)
                .withSchedule(Schedule.forInterval(intervalInMillis, firstRun))
                .withRunMode(RunMode.RUN_ONCE_PER_CLUSTER);

        try
        {
            final JobId jobId = toJobId(jobID);
            final JobDetails existing = schedulerService.getJobDetails(jobId);
            if (existing != null)
            {
                LOG.error("We will be replacing an existing job with jobId=" + jobId + ": " + existing);
                schedulerService.unscheduleJob(jobId);
            }
            schedulerService.scheduleJob(jobId, jobConfig);
            LOG.error("Successfully scheduled jobId=" + jobId);
        }
        catch (SchedulerServiceException sse)
        {
            throw new Exception("Unable to create schedule for job ID '" + jobID + '\'', sse);
        }
    }

    private static JobId toJobId(int jobID)
    {
        return JobId.of(JOB_ID_PREFIX + jobID);
    }

}


public class JobRunnerA implements JobRunner {

    private static final Logger LOG = LoggerFactory.getLogger(JobRunnerA.class);

    @Nullable
    @Override
    public JobRunnerResponse runJob(JobRunnerRequest jobRunnerRequest) {
        LOG.info("------------------JOB A----------------");
        return JobRunnerResponse.success();
    }
}


public class JobRunnerB implements JobRunner {

    private static final Logger LOG = LoggerFactory.getLogger(JobRunnerB.class);

    @Nullable
    @Override
    public JobRunnerResponse runJob(JobRunnerRequest jobRunnerRequest) {
        LOG.info("------------------JOB B----------------");
        return JobRunnerResponse.success();
    }
}

 

Suggest an answer

Log in or Join to answer
Community showcase
Piotr Plewa
Published Dec 27, 2017 in Bitbucket

Recipe: Deploying AWS Lambda functions with Bitbucket Pipelines

Bitbucket Pipelines helps me manage and automate a number of serverless deployments to AWS Lambda and this is how I do it. I'm building Node.js Lambda functions using node-lambda&nbsp...

710 views 0 4
Read article

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you
Atlassian Team Tour

Join us on the Team Tour

We're bringing product updates and pro tips on teamwork to ten cities around the world.

Save your spot