Skip to content

Server expires own jobs #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby-2.1.4
ruby-2.3.0
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Accept `expired_at` from the client in order to do our own cleaning (and not rely on getting delete requests)

## [1.1.0] - 2016-03-21
### Added
- Re-raise error if job worker runs into one. Makes errors more visible.
Expand Down
2 changes: 1 addition & 1 deletion MIT-LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2015 G5
Copyright 2016 G5

MIT License

Expand Down
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,37 @@ If you use `protected_attributes`, in an initializer:
Asyncapi::Server::Job.attr_accessible :status, :callback_url, :class_name, :params, :secret
```

## Usage without Asyncapi::Client

If you want to use this without asyncapi client, you need to prepare two things: the endpoint that asyncapi-server will reply to.

Create the job by POSTing the following to CreateSomething above:

```json
{
"job": {
"callback_url": "https://myclient.com/jobs_callback",
"params": {
"name": "Something's name",
"approved": true
},
"secret": "A secret unique to this job, so that you know what job the server is referring to"
}
}
```

When the server is done processing, it will post something to your client. Your endpoint must accept the following json as the body:

```
{
"job": {
"status": "success",
"message": "The output of the Runner class (i.e. `CreateSomething`)",
"secret": "The secret you had sent earlier (this is how you can be sure it's not someone else updating your endpoint)",
}
}
```

### RSpec

If you want to create an integration spec for you Asyncapi server endpoint, make sure you require the helper:
Expand All @@ -70,9 +101,15 @@ asyncapi_post("/api/v1/long_running_job", name: "Compute")

This helper calls `post` underneath but builds the request in a way that Asyncapi server understands.

## Development

- Run `rake db:migrate && rake db:migrate RAILS_ENV=test`
- Make changes
- `rspec`

## License

Copyright (c) 2015 G5
Copyright (c) 2016 G5

MIT License

Expand Down
4 changes: 4 additions & 0 deletions app/models/asyncapi/server/job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class Job < ActiveRecord::Base

self.table_name = "asyncapi_server_jobs"
enum status: %i[queued success error]
scope :expired, -> do
expired_at = arel_table[:expired_at]
where.not(expired_at: nil).where(expired_at.lt(Time.now))
end

def url
Engine.routes.url_helpers.v1_job_url(self)
Expand Down
2 changes: 1 addition & 1 deletion app/serializers/asyncapi/server/job_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Asyncapi
module Server
class JobSerializer < ActiveModel::Serializer

attributes :id, :url, :secret
attributes :id, :url, :secret, :expired_at

end
end
Expand Down
22 changes: 22 additions & 0 deletions app/workers/asyncapi/server/cleaner_worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Asyncapi
module Server
class CleanerWorker

include Sidekiq::Worker
sidekiq_options retry: false

def perform
Job.expired.find_each(&:destroy)
end

end
end
end

if Sidekiq.server?
Sidekiq::Cron::Job.create({
name: "Delete expired jobs",
cron: Asyncapi::Server.clean_job_cron,
klass: Asyncapi::Server::CleanerWorker.name,
})
end
6 changes: 4 additions & 2 deletions asyncapi_server.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ Gem::Specification.new do |s|

s.add_dependency "kaminari"
s.add_dependency "api-pagination"
s.add_dependency "rails"
s.add_dependency "active_model_serializers"
s.add_dependency "rails", "~> 4.0"
s.add_dependency "active_model_serializers", "~> 0.9.0"
s.add_dependency "typhoeus"
s.add_dependency "sidekiq"
s.add_dependency "sidekiq-cron"
s.add_dependency "responders", "~> 2.0"

s.add_development_dependency "sqlite3", "~> 1.3.10"
s.add_development_dependency "rspec-rails", "~> 3.1.0"
Expand Down
11 changes: 0 additions & 11 deletions db/migrate/20150130062520_add_expired_at_to_asyncapi_server_job.rb

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddExpiredAtToAsyncapiServerJobs < ActiveRecord::Migration
def change
add_column :asyncapi_server_jobs, :expired_at, :datetime
end
end
4 changes: 3 additions & 1 deletion lib/asyncapi/server.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
require 'active_model_serializers'
require 'responders'
require "asyncapi/server/engine"

module Asyncapi
module Server

CONFIGURATION = {
expiry_threshold: 10.days,
clean_job_cron: "0 * * * *",
}

CONFIGURATION.each do |var, default|
Expand Down
1 change: 1 addition & 0 deletions lib/asyncapi/server/rails_ext/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def job_params_with(class_name)
:class_name,
:params,
:secret,
:expired_at,
)
end

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

15 changes: 8 additions & 7 deletions spec/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20150201231329) do
ActiveRecord::Schema.define(version: 20160721002627) do

create_table "asyncapi_server_jobs", force: true do |t|
t.integer "status"
t.string "callback_url"
t.string "class_name"
t.text "params"
t.string "secret"
create_table "asyncapi_server_jobs", force: :cascade do |t|
t.integer "status"
t.string "callback_url"
t.string "class_name"
t.text "params"
t.string "secret"
t.datetime "expired_at"
end

end
19 changes: 19 additions & 0 deletions spec/models/job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ module Server
end
end

describe ".expired" do
let!(:job_1) do
create(:asyncapi_server_job, expired_at: 2.minutes.ago)
end
let!(:job_2) do
create(:asyncapi_server_job, expired_at: 2.minutes.from_now)
end
let!(:job_3) do
create(:asyncapi_server_job, expired_at: 2.minutes.ago)
end
let!(:job_4) do
create(:asyncapi_server_job, expired_at: nil)
end

it "returns expired job" do
expect(Job.expired).to match_array([job_1, job_3])
end
end

end
end
end
12 changes: 9 additions & 3 deletions spec/requests/enqueueing_jobs_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,38 @@

describe "Enqueueing jobs", type: :request do

let(:job) { build_stubbed(:asyncapi_server_job, secret: "secret") }
let(:job) do
build_stubbed(:asyncapi_server_job, secret: "secret", expired_at: expired_at)
end
let(:expired_at) { 2.days.from_now }

before do
allow(Asyncapi::Server::Job).to receive(:create).with(
class_name: "Runner",
callback_url: "callback_url",
params: {client: "params"}.to_json,
secret: "secret",
expired_at: expired_at.to_s,
).and_return(job)

allow(job).to receive(:url).and_return("server_job_url")
end

it "allows asynchronous handing of http requests and cleans up old jobs" do
it "allows asynchronous handling of http requests and cleans up old jobs" do
expect(Asyncapi::Server::JobWorker).to receive(:perform_async).with(job.id)

post("tests", job: {
post("/tests", job: {
callback_url: "callback_url",
params: {client: "params"}.to_json,
secret: "secret",
expired_at: expired_at,
})

expect(response).to be_successful
parsed_response = indifferent_hash(response.body)[:job]
expect(parsed_response[:url]).to eq "server_job_url"
expect(parsed_response[:secret]).to eq "secret"
expect(Time.parse(parsed_response[:expired_at]).to_i).to eq expired_at.to_i
end

end
4 changes: 3 additions & 1 deletion spec/serializers/job_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
module Asyncapi::Server
describe JobSerializer do

let(:job) { build_stubbed(:asyncapi_server_job) }
let(:expired_at) { 2.days.from_now }
let(:job) { build_stubbed(:asyncapi_server_job, expired_at: expired_at) }
let(:serializer) { described_class.new(job) }
subject(:serialized_hash) { serializer.attributes }

its([:id]) { is_expected.to eq job.id }
its([:expired_at]) { is_expected.to eq expired_at }

it "has a url" do
allow(job).to receive(:url).and_return("url")
Expand Down
15 changes: 0 additions & 15 deletions spec/server_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,5 @@
module Asyncapi
describe Server do

describe ".expiry_threshold=" do
let!(:original_expiry_threshold) { described_class.expiry_threshold }
after { described_class.expiry_threshold = original_expiry_threshold }
it "sets the threshold for expiring old jobs" do
described_class.expiry_threshold = 2.days
expect(described_class.expiry_threshold).to eq 2.days
end
end

describe ".expiry_threshold" do
it "defaults to 10 days" do
expect(described_class.expiry_threshold).to eq 10.days
end
end

end
end
33 changes: 33 additions & 0 deletions spec/workers/cleaner_worker_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'spec_helper'

module Asyncapi
module Server
RSpec.describe CleanerWorker do

it "does not retry" do
expect(described_class.sidekiq_options_hash['retry']).to be false
end

describe "#perform" do
let!(:job_1) do
create(:asyncapi_server_job, expired_at: 2.minutes.ago)
end
let!(:job_2) do
create(:asyncapi_server_job, expired_at: 2.minutes.from_now)
end
let!(:job_3) do
create(:asyncapi_server_job, expired_at: 2.minutes.ago)
end
let!(:job_4) do
create(:asyncapi_server_job, expired_at: nil)
end

it "deletes jobs that are expired" do
described_class.new.perform
expect(Job.all).to match_array([job_2, job_4])
end
end

end
end
end