Skip to content

Commit

Permalink
v1 Scheduled Jobs / Third Party Sync Migration (#171)
Browse files Browse the repository at this point in the history
* Added a scheduled job manager to handle the creation of cron jobs located in application.properties

* Added main logic, storing jobs to database and parsing them on retrieval / updating. Added job and trigger listeners to handle updating the job status before and after execution.

* Added ScheduledJobDTO for API endpoints and handled a possible row insert race condition exception.

* Fix failing maven tests.

* Added SQL migration. Removed isAllowedExecutionOverlap, as the DisallowConcurrentExecution annotation can be applied to the job.

* Changed conditional property to match current quartz condition.

* Attached the job properties to the Job Type in the enum definition

* Added Slack Notification for failed third party sync

* Fix failing maven tests

* Trigger mvn tests again

* Fix maven tests ?

* Fix exception thrown if @value is missing

* Changed condition on property value

* Send pollable task ID in notification and other small changes.

* Denormalized job type and job status. Added version to base scheduled job properties

* Cleanup & Set end / start date to null depending on current job status

* Added more ScheduledJob endpoints and veto the job execution if its disabled.

* Added ScheduledJobResponse for structured API responses

* Use UUID for job id

* Fix failing tests

* Fix deadlock that would occur in audit table when two syncs started at the same time

* Clean up map

* Refactoring & Added PagerDuty incident creation on job failure

* Add flyway schema & Add PD incident title to configuration

* Added configuration examples, Fix deadlock occurring at the method level, Made endpoints require POST request

* Comment changes

* Add starting tests, ensuring they work in HSQL

* Tests check

* Fix tests by not using new application context

* Fix tests by manually adding rows flyway would handle that HSQL doesn't do

* Review comments to fix up SQL schema

* Review comments, ZonedDateTime & Foreign Key, BIT changes

* Capture PathVariable using UUID class

* Review comment changes and refactoring

* Revert scheduler change

* Change endpoints back to POST methods

* Rename Job Status and Type, extra logging

* Use HashSet instead of list, throw run time error if duplicate uuids are found

* Custom timeout for third party sync in application.properties

* Add metric for third party syncs that were scheduled and pagerduty incidents/resolves that fail.

* Renamed Status enum for WS response, disallow triggering disabled jobs

* Added auto incremented primary key

* Fix tests

* Small refactor

* Remove unique key, added insert statements for hsqldb

* Log pollable task and scheduled job url on PD failure

* Added Integration Tests, Unique key on UUID column

* Constructor Autowire for WS

* Update tests to use ID watcher and removed redundant tests

* Adding queuing up a manual re-trigger

* Only set job status as Scheduled if the job was only inserted into the db now

* Extend base entity and rename to isEnabled

* Use waitForPollableTask instead of task.get()
  • Loading branch information
mattwilshire authored Nov 15, 2024
1 parent 1a11c98 commit 7234882
Show file tree
Hide file tree
Showing 26 changed files with 1,751 additions and 1 deletion.
160 changes: 160 additions & 0 deletions webapp/src/main/java/com/box/l10n/mojito/entity/ScheduledJob.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package com.box.l10n.mojito.entity;

import com.box.l10n.mojito.json.ObjectMapper;
import com.box.l10n.mojito.service.scheduledjob.ScheduledJobProperties;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import java.time.ZonedDateTime;
import org.hibernate.envers.Audited;

@Audited
@Entity
@Table(name = "scheduled_job")
public class ScheduledJob extends BaseEntity {
@Basic(optional = false)
@Column(name = "uuid")
private String uuid;

@ManyToOne
@JoinColumn(
name = "repository_id",
foreignKey = @ForeignKey(name = "FK__SCHEDULED_JOB__IMPORT_REPOSITORY__ID"))
private Repository repository;

@ManyToOne
@JoinColumn(name = "job_type", foreignKey = @ForeignKey(name = "FK__JOB_TYPE__JOB_TYPE_ID"))
private ScheduledJobType jobType;

@Column(name = "cron")
private String cron;

@Transient private ScheduledJobProperties properties;

@Basic(optional = false)
@Column(name = "properties")
private String propertiesString;

@ManyToOne
@JoinColumn(name = "job_status", foreignKey = @ForeignKey(name = "FK__JOB_STATUS__JOB_STATUS_ID"))
private ScheduledJobStatus jobStatus;

@Column(name = "start_date")
private ZonedDateTime startDate;

@Column(name = "end_date")
private ZonedDateTime endDate;

@Basic(optional = false)
@Column(name = "enabled")
private Boolean enabled = true;

@PostLoad
public void deserializeProperties() {
ObjectMapper objectMapper = new ObjectMapper();
try {
this.properties =
objectMapper.readValue(propertiesString, jobType.getEnum().getPropertiesClass());
} catch (Exception e) {
throw new RuntimeException(
"Failed to deserialize properties '"
+ propertiesString
+ "' for class: "
+ jobType.getEnum().getPropertiesClass().getName(),
e);
}
}

public String getUuid() {
return uuid;
}

public void setUuid(String uuid) {
this.uuid = uuid;
}

public Repository getRepository() {
return repository;
}

public void setRepository(Repository repository) {
this.repository = repository;
}

public ScheduledJobType getJobType() {
return jobType;
}

public void setJobType(ScheduledJobType jobType) {
this.jobType = jobType;
}

public String getCron() {
return cron;
}

public void setCron(String cron) {
this.cron = cron;
}

public ScheduledJobProperties getProperties() {
return properties;
}

public void setProperties(ScheduledJobProperties properties) {
this.properties = properties;

ObjectMapper objectMapper = new ObjectMapper();
try {
this.propertiesString = objectMapper.writeValueAsString(this.properties);
} catch (Exception e) {
throw new RuntimeException("Failed to serialize properties", e);
}
}

public String getPropertiesString() {
return propertiesString;
}

public void setPropertiesString(String properties) {
this.propertiesString = properties;
}

public ScheduledJobStatus getJobStatus() {
return jobStatus;
}

public void setJobStatus(ScheduledJobStatus jobStatus) {
this.jobStatus = jobStatus;
}

public ZonedDateTime getStartDate() {
return startDate;
}

public void setStartDate(ZonedDateTime startDate) {
this.startDate = startDate;
}

public ZonedDateTime getEndDate() {
return endDate;
}

public void setEndDate(ZonedDateTime endDate) {
this.endDate = endDate;
}

public Boolean isEnabled() {
return enabled;
}

public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.box.l10n.mojito.entity;

import static org.hibernate.envers.RelationTargetAuditMode.NOT_AUDITED;

import com.box.l10n.mojito.rest.View;
import com.fasterxml.jackson.annotation.JsonView;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.envers.Audited;

@Entity
@Table(name = "scheduled_job_status_type")
@Audited(targetAuditMode = NOT_AUDITED)
public class ScheduledJobStatus extends BaseEntity {
@Id private Long id;

@Basic(optional = false)
@Column(name = "name")
@Enumerated(EnumType.STRING)
@JsonView(View.Repository.class)
private com.box.l10n.mojito.service.scheduledjob.ScheduledJobStatus jobStatus;

public com.box.l10n.mojito.service.scheduledjob.ScheduledJobStatus getEnum() {
return jobStatus;
}

public void setJobStatus(com.box.l10n.mojito.service.scheduledjob.ScheduledJobStatus jobStatus) {
this.jobStatus = jobStatus;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.box.l10n.mojito.entity;

import static org.hibernate.envers.RelationTargetAuditMode.NOT_AUDITED;

import com.box.l10n.mojito.rest.View;
import com.fasterxml.jackson.annotation.JsonView;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.envers.Audited;

@Entity
@Table(name = "scheduled_job_type")
@Audited(targetAuditMode = NOT_AUDITED)
public class ScheduledJobType extends BaseEntity {
@Id private Long id;

@Basic(optional = false)
@Column(name = "name")
@Enumerated(EnumType.STRING)
@JsonView(View.Repository.class)
private com.box.l10n.mojito.service.scheduledjob.ScheduledJobType jobType;

@Override
public Long getId() {
return id;
}

@Override
public void setId(Long id) {
this.id = id;
}

public com.box.l10n.mojito.service.scheduledjob.ScheduledJobType getEnum() {
return jobType;
}

public void setEnum(com.box.l10n.mojito.service.scheduledjob.ScheduledJobType jobType) {
this.jobType = jobType;
}
}
Loading

0 comments on commit 7234882

Please sign in to comment.