Skip to content

Commit

Permalink
Merge c849436 into 59252f1
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed Feb 6, 2020
2 parents 59252f1 + c849436 commit 8028d20
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 82 deletions.
1 change: 0 additions & 1 deletion lib/lyber_core.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

require 'dor-services'
require 'lyber_core/log'
require 'lyber_core/robot'
require 'lyber_core/return_state'
77 changes: 35 additions & 42 deletions lib/lyber_core/robot.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# frozen_string_literal: true

require 'benchmark'
require 'active_support'
require 'active_support/core_ext'
require 'active_support/core_ext/string/inflections' # camelcase

module LyberCore
module Robot
Expand All @@ -21,56 +18,41 @@ def perform(druid)
end
end

# Converts a given step to the Robot class name
# Examples:
#
# - `dor:assemblyWF:jp2-create` into `Robots::DorRepo::Assembly::Jp2Create`
# - `dor:gisAssemblyWF:start-assembly-workflow` into `Robots::DorRepo::GisAssembly::StartAssemblyWorkflow`
# - `dor:etdSubmitWF:binder-transfer` into `Robots:DorRepo::EtdSubmit::BinderTransfer`
#
# @param [String] step. fully qualified step name, e.g., `dor:accessionWF:descriptive-metadata`
# @param [Hash] opts
# @option :repo_suffix defaults to `Repo`
# @return [String] The class name for the robot, e.g., `Robots::DorRepo::Accession:DescriptiveMetadata`
def self.step_to_classname(step, opts = {})
# generate the robot job class name
opts[:repo_suffix] ||= 'Repo'
r, w, s = step.split(/:/, 3)
[
'Robots',
r.camelcase + opts[:repo_suffix], # 'Dor' conflicts with dor-services
w.sub('WF', '').camelcase,
s.tr('-', '_').camelcase
].join('::')
end

attr_accessor :check_queued_status
attr_reader :workflow_service
attr_reader :workflow_name, :process

def initialize(repo, workflow_name, step_name, opts = {})
def initialize(workflow_name, process, check_queued_status: true)
Signal.trap('QUIT') { puts "#{Process.pid} ignoring SIGQUIT" } # SIGQUIT ignored to let the robot finish
@repo = repo
@workflow_name = workflow_name
@step_name = step_name
@check_queued_status = opts.fetch(:check_queued_status, true)
@workflow_service = opts.fetch(:workflow_service) { Dor::Config.workflow.client }
@process = process
@check_queued_status = check_queued_status
end

def workflow_service
raise "The workflow_service method must be implemented on the class that includes LyberCore::Robot"
end

# Sets up logging, timing and error handling of the job
# Calls the #perform method, then sets workflow to 'completed' or 'error' depending on success
def work(druid)
Honeybadger.context(druid: druid, step_name: @step_name, workflow_name: @workflow_name) if defined? Honeybadger
Honeybadger.context(druid: druid, process: process, workflow_name: workflow_name) if defined? Honeybadger

LyberCore::Log.set_logfile($stdout) # let process manager(bluepill) handle logging
LyberCore::Log.info "#{druid} processing"
return if @check_queued_status && !item_queued?(druid)
return if check_queued_status && !item_queued?(druid)

# this is the default note to pass back to workflow service, but it can be overriden by a robot that uses the Lybercore::Robot::ReturnState object to return a status
# this is the default note to pass back to workflow service,
# but it can be overriden by a robot that uses the Lybercore::Robot::ReturnState
# object to return a status
note = Socket.gethostname

# update the workflow status to indicate that started
puts('setting to start')
workflow_service.update_status(druid: druid, workflow: @workflow_name, process: @step_name, status: 'started', elapsed: 1.0, note: note)
workflow_service.update_status(druid: druid,
workflow: workflow_name,
process: process,
status: 'started',
elapsed: 1.0,
note: note)

result = nil
elapsed = Benchmark.realtime do
Expand All @@ -87,25 +69,36 @@ def work(druid)
workflow_state = 'completed'
end
# update the workflow status from its current state to the state returned by perform (or 'completed' as the default)
workflow_service.update_status(druid: druid, workflow: @workflow_name, process: @step_name, status: workflow_state, elapsed: elapsed, note: note)
workflow_service.update_status(druid: druid,
workflow: workflow_name,
process: process,
status: workflow_state,
elapsed: elapsed,
note: note)
LyberCore::Log.info "Finished #{druid} in #{sprintf('%0.4f', elapsed)}s"
rescue StandardError => e
Honeybadger.notify(e) if defined? Honeybadger
begin
LyberCore::Log.error e.message + "\n" + e.backtrace.join("\n")
workflow_service.update_error_status(druid: druid, workflow: @workflow_name, process: @step_name, error_msg: e.message, error_text: Socket.gethostname)
workflow_service.update_error_status(druid: druid,
workflow: workflow_name,
process: process,
error_msg: e.message,
error_text: Socket.gethostname)
rescue StandardError => e
LyberCore::Log.error "Cannot set #{druid} to status='error'\n" + e.message + "\n" + e.backtrace.join("\n")
LyberCore::Log.error "Cannot set #{druid} to status='error'\n#{e.message}\n#{e.backtrace.join("\n")}"
raise e # send exception to Resque failed queue
end
end

private

def item_queued?(druid)
status = workflow_service.workflow_status(druid: druid, workflow: @workflow_name, process: @step_name)
status = workflow_service.workflow_status(druid: druid,
workflow: workflow_name,
process: process)
return true if status =~ /queued/i
msg = "Item #{druid} is not queued for #{@step_name} (#{@workflow_name}), but has status of '#{status}'. Will skip processing"
msg = "Item #{druid} is not queued for #{process} (#{workflow_name}), but has status of '#{status}'. Will skip processing"
Honeybadger.notify(msg) if defined? Honeybadger
LyberCore::Log.warn msg
false
Expand Down
7 changes: 1 addition & 6 deletions lyber-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib)

Gem::Specification.new do |s|
s.name = 'lyber-core'
s.version = '5.5.1'
s.version = '6.0.0'
s.licenses = ['Apache-2.0']
s.platform = Gem::Platform::RUBY
s.authors = ['Alpana Pande', 'Bess Sadler', 'Chris Fitzpatrick', 'Douglas Kim', 'Richard Anderson', 'Willy Mene', 'Michael Klein', 'Darren Weber', 'Peter Mangiafico']
Expand All @@ -17,11 +17,6 @@ Gem::Specification.new do |s|

s.required_rubygems_version = '>= 1.3.6'

# Runtime dependencies
s.add_dependency 'activesupport'
s.add_dependency 'dor-services', '>= 7.0.0', '< 9' # Used for building a dor-workflow-client connection
s.add_dependency 'dor-workflow-client', '~> 3.11'

# Bundler will install these gems too if you've checked out lyber-core source from git and run 'bundle install'
# It will not add these as dependencies if you require lyber-core for other projects
s.add_development_dependency 'coveralls'
Expand Down
67 changes: 34 additions & 33 deletions spec/lyber_core/robots/robot_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@
let(:wf_name) { 'testWF' }
let(:step_name) { 'test-step' }
let(:workflow_client) do
instance_double(Dor::Workflow::Client, update_status: true, update_error_status: true)
double('Dor::Workflow::Client', update_status: true, update_error_status: true)
end

shared_examples '#perform' do
let(:test_class) { test_robot } # default
let(:logged) { capture_stdout { test_class.perform druid } } # Note that this is what invokes the robot
describe LyberCore::Robot do
let(:test_robot) do
Class.new do
include LyberCore::Robot
def perform(_druid)
LyberCore::Log.info 'work done!'
end
end
end

let(:robot) { test_robot.new('testWF', 'test-step') }
let(:logged) { capture_stdout { robot.work druid } } # Note that this is what invokes the robot
before do
allow(Dor::Config.workflow).to receive(:client).and_return(workflow_client)
allow(robot).to receive(:workflow_service).and_return(workflow_client)
allow(workflow_client).to receive(:workflow_status).with(druid: druid, workflow: wf_name, process: step_name).and_return('queued')
end

Expand All @@ -30,10 +39,12 @@
end

context 'correct state returned' do
let(:test_class) do
Class.new(test_robot) do
let(:test_robot) do
Class.new do
include LyberCore::Robot

def perform(_druid)
super && LyberCore::Robot::ReturnState.new(status: 'skipped')
LyberCore::Log.info('work done!') && LyberCore::Robot::ReturnState.new(status: 'skipped')
end
end
end
Expand All @@ -51,10 +62,12 @@ def perform(_druid)
end

context 'when correct state and a note returned' do
let(:test_class) do
Class.new(test_robot) do
let(:test_robot) do
Class.new do
include LyberCore::Robot

def perform(_druid)
super && LyberCore::Robot::ReturnState.new(note: 'some note to pass back to workflow')
LyberCore::Log.info('work done!') && LyberCore::Robot::ReturnState.new(note: 'some note to pass back to workflow')
end
end
end
Expand Down Expand Up @@ -82,10 +95,12 @@ def perform(_druid)
end

context 'when skipped state and a note returned' do
let(:test_class) do
Class.new(test_robot) do
let(:test_robot) do
Class.new do
include LyberCore::Robot

def perform(_druid)
super && LyberCore::Robot::ReturnState.new(status: 'skipped', note: 'some note to pass back to workflow')
LyberCore::Log.info('work done!') && LyberCore::Robot::ReturnState.new(status: 'skipped', note: 'some note to pass back to workflow')
end
end
end
Expand All @@ -103,10 +118,12 @@ def perform(_druid)
end

context 'using a ReturnState constant' do
let(:test_class) do
Class.new(test_robot) do
let(:test_robot) do
Class.new do
include LyberCore::Robot

def perform(_druid)
super && LyberCore::Robot::ReturnState.SKIPPED
LyberCore::Log.info('work done!') && LyberCore::Robot::ReturnState.SKIPPED
end
end
end
Expand Down Expand Up @@ -145,20 +162,4 @@ def perform(_druid)
expect(logged).to match /Item druid\:.* is not queued.*completed/m
end
end

describe LyberCore::Robot do
let(:test_robot) do
Class.new do
include LyberCore::Robot
def initialize
super('dor', 'testWF', 'test-step')
end

def perform(_druid)
LyberCore::Log.info 'work done!'
end
end
end
it_behaves_like '#perform'
end
end

0 comments on commit 8028d20

Please sign in to comment.