Added database locking to prevent race conditions.

This commit is contained in:
Martin Edenhofer 2016-09-08 21:18:26 +02:00
parent 18cc4af6b8
commit eceb9889b1
6 changed files with 160 additions and 109 deletions

View file

@ -535,11 +535,14 @@ class ApplicationController < ActionController::Base
clean_params = object.param_association_lookup(params)
clean_params = object.param_cleanup(clean_params, true)
# save object
generic_object.update_attributes!(clean_params)
generic_object.with_lock do
# set relations
generic_object.param_set_associations(params)
# set attributes
generic_object.update_attributes!(clean_params)
# set relations
generic_object.param_set_associations(params)
end
if params[:expand]
render json: generic_object.attributes_with_relation_names, status: :ok

View file

@ -114,25 +114,26 @@ class TicketsController < ApplicationController
# create ticket
ticket.save!
ticket.with_lock do
# create tags if given
if params[:tags] && !params[:tags].empty?
tags = params[:tags].split(/,/)
tags.each { |tag|
Tag.tag_add(
object: 'Ticket',
o_id: ticket.id,
item: tag,
created_by_id: current_user.id,
)
}
# create tags if given
if params[:tags] && !params[:tags].empty?
tags = params[:tags].split(/,/)
tags.each { |tag|
Tag.tag_add(
object: 'Ticket',
o_id: ticket.id,
item: tag,
created_by_id: current_user.id,
)
}
end
# create article if given
if params[:article]
article_create(ticket, params[:article])
end
end
# create article if given
if params[:article]
article_create(ticket, params[:article])
end
# create links (e. g. in case of ticket split)
# links: {
# Ticket: {
@ -191,10 +192,11 @@ class TicketsController < ApplicationController
}
end
ticket.update_attributes!(clean_params)
if params[:article]
article_create(ticket, params[:article])
ticket.with_lock do
ticket.update_attributes!(clean_params)
if params[:article]
article_create(ticket, params[:article])
end
end
if params[:expand]

View file

@ -254,29 +254,31 @@ class UsersController < ApplicationController
# permission check
permission_check_by_permission(params)
user.update_attributes(clean_params)
user.with_lock do
user.update_attributes(clean_params)
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:role_ids] || params[:roles])
user.role_ids = params[:role_ids]
user.param_set_associations({ role_ids: params[:role_ids], roles: params[:roles] })
end
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:role_ids] || params[:roles])
user.role_ids = params[:role_ids]
user.param_set_associations({ role_ids: params[:role_ids], roles: params[:roles] })
end
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:group_ids] || params[:groups])
user.group_ids = params[:group_ids]
user.param_set_associations({ group_ids: params[:group_ids], groups: params[:groups] })
end
# only allow Admin's
if current_user.permissions?('admin.user') && (params[:group_ids] || params[:groups])
user.group_ids = params[:group_ids]
user.param_set_associations({ group_ids: params[:group_ids], groups: params[:groups] })
end
# only allow Admin's and Agent's
if current_user.permissions?(['admin.user', 'ticket.agent']) && (params[:organization_ids] || params[:organizations])
user.param_set_associations({ organization_ids: params[:organization_ids], organizations: params[:organizations] })
end
# only allow Admin's and Agent's
if current_user.permissions?(['admin.user', 'ticket.agent']) && (params[:organization_ids] || params[:organizations])
user.param_set_associations({ organization_ids: params[:organization_ids], organizations: params[:organizations] })
end
if params[:expand]
user = User.find(user.id).attributes_with_relation_names
render json: user, status: :ok
return
if params[:expand]
user = User.find(user.id).attributes_with_relation_names
render json: user, status: :ok
return
end
end
# get new data
@ -704,7 +706,7 @@ curl http://localhost/api/v1/users/password_change.json -v -u #{login}:#{passwor
render json: { message: 'failed', notice: ['Current password needed!'] }, status: :ok
return
end
user = User.authenticate( current_user.login, params[:password_old] )
user = User.authenticate(current_user.login, params[:password_old])
if !user
render json: { message: 'failed', notice: ['Current password is wrong!'] }, status: :ok
return
@ -763,10 +765,12 @@ curl http://localhost/api/v1/users/preferences.json -v -u #{login}:#{password} -
if params[:user]
user = User.find(current_user.id)
params[:user].each { |key, value|
user.preferences[key.to_sym] = value
}
user.save
user.with_lock do
params[:user].each { |key, value|
user.preferences[key.to_sym] = value
}
user.save
end
end
render json: { message: 'ok' }, status: :ok
end

View file

@ -442,10 +442,10 @@ retrns
# get ticket# based on email headers
if mail[ 'x-zammad-ticket-id'.to_sym ]
ticket = Ticket.find_by( id: mail[ 'x-zammad-ticket-id'.to_sym ] )
ticket = Ticket.find_by(id: mail[ 'x-zammad-ticket-id'.to_sym ])
end
if mail[ 'x-zammad-ticket-number'.to_sym ]
ticket = Ticket.find_by( number: mail[ 'x-zammad-ticket-number'.to_sym ] )
ticket = Ticket.find_by(number: mail[ 'x-zammad-ticket-number'.to_sym ])
end
# set ticket state to open if not new
@ -500,7 +500,6 @@ retrns
priority_id: Ticket::Priority.find_by(name: '2 normal').id,
preferences: preferences,
)
set_attributes_by_x_headers(ticket, 'ticket', mail)
# create ticket
@ -508,45 +507,47 @@ retrns
end
# set attributes
article = Ticket::Article.new(
ticket_id: ticket.id,
type_id: Ticket::Article::Type.find_by(name: 'email').id,
sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
content_type: mail[:content_type],
body: mail[:body],
from: mail[:from],
to: mail[:to],
cc: mail[:cc],
subject: mail[:subject],
message_id: mail[:message_id],
internal: false,
)
ticket.with_lock do
article = Ticket::Article.new(
ticket_id: ticket.id,
type_id: Ticket::Article::Type.find_by(name: 'email').id,
sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
content_type: mail[:content_type],
body: mail[:body],
from: mail[:from],
to: mail[:to],
cc: mail[:cc],
subject: mail[:subject],
message_id: mail[:message_id],
internal: false,
)
# x-headers lookup
set_attributes_by_x_headers(article, 'article', mail)
# x-headers lookup
set_attributes_by_x_headers(article, 'article', mail)
# create article
article.save!
# create article
article.save!
# store mail plain
Store.add(
object: 'Ticket::Article::Mail',
o_id: article.id,
data: msg,
filename: "ticket-#{ticket.number}-#{article.id}.eml",
preferences: {}
)
# store mail plain
Store.add(
object: 'Ticket::Article::Mail',
o_id: article.id,
data: msg,
filename: "ticket-#{ticket.number}-#{article.id}.eml",
preferences: {}
)
# store attachments
if mail[:attachments]
mail[:attachments].each do |attachment|
Store.add(
object: 'Ticket::Article',
o_id: article.id,
data: attachment[:data],
filename: attachment[:filename],
preferences: attachment[:preferences]
)
# store attachments
if mail[:attachments]
mail[:attachments].each do |attachment|
Store.add(
object: 'Ticket::Article',
o_id: article.id,
data: attachment[:data],
filename: attachment[:filename],
preferences: attachment[:preferences]
)
end
end
end
end

View file

@ -294,25 +294,30 @@ returns
def self.send(client_id, data)
path = "#{@path}/#{client_id}/"
filename = "send-#{Time.now.utc.to_f}"
location = "#{path}#{filename}"
check = true
count = 0
while check
if File.exist?(path + filename)
if File.exist?(location)
count += 1
filename = "#{filename}-#{count}"
location = "#{path}#{filename}-#{count}"
else
check = false
end
end
return false if !File.directory? path
File.open(path + 'a-' + filename, 'wb') { |file|
file.flock(File::LOCK_EX)
file.write data.to_json
file.flock(File::LOCK_UN)
file.close
}
return false if !File.exist?(path + 'a-' + filename)
FileUtils.mv(path + 'a-' + filename, path + filename)
begin
File.open(location, 'wb') { |file|
file.flock(File::LOCK_EX)
file.write data.to_json
file.flock(File::LOCK_UN)
file.close
}
rescue => e
log('error', e.inspect)
log('error', "error in writing message file '#{location}'")
return false
end
true
end

View file

@ -125,7 +125,7 @@ class TestCase < Test::Unit::TestCase
def screenshot(params)
instance = params[:browser] || @browser
comment = params[:comment] || ''
filename = "tmp/#{Time.zone.now.strftime('screenshot_%Y_%m_%d__%H_%M_%S')}_#{comment}_#{instance.hash}.png"
filename = "tmp/#{Time.zone.now.strftime('screenshot_%Y_%m_%d__%H_%M_%S_%L')}_#{comment}#{instance.hash}.png"
log('screenshot', { filename: filename })
instance.save_screenshot(filename)
end
@ -415,6 +415,7 @@ class TestCase < Test::Unit::TestCase
log('click', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'click_before')
if params[:css]
begin
@ -501,6 +502,7 @@ class TestCase < Test::Unit::TestCase
modal_disappear(
browser: browser1,
timeout: 12, # default 6
)
=end
@ -514,7 +516,7 @@ class TestCase < Test::Unit::TestCase
watch_for_disappear(
browser: instance,
css: '.modal',
timeout: 6,
timeout: params[:timeout] || 6,
)
end
@ -599,6 +601,7 @@ class TestCase < Test::Unit::TestCase
log('set', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'set_before')
element = instance.find_elements(css: params[:css])[0]
if !params[:no_click]
@ -616,11 +619,24 @@ class TestCase < Test::Unit::TestCase
}
end
# it's not working stable with ff via selenium, use js
if browser =~ /firefox/i && params[:css] =~ /\[data-name=/
log('set_ff_check', params)
value = instance.find_elements(css: params[:css])[0].text
if value != params[:value]
log('set_ff_check_failed_use_js', params)
value_quoted = quote(params[:value])
puts "DEBUG $('#{params[:css]}').html('#{value_quoted}').trigger('focusout')"
instance.execute_script("$('#{params[:css]}').html('#{value_quoted}').trigger('focusout')")
end
end
if params[:blur]
instance.execute_script("$('#{params[:css]}').blur()")
end
sleep 0.2
screenshot(browser: instance, comment: 'set_after')
end
=begin
@ -639,6 +655,7 @@ class TestCase < Test::Unit::TestCase
log('select', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'select_before')
# searchable select
element = instance.find_elements(css: "#{params[:css]}.js-shadow")[0]
@ -677,6 +694,7 @@ class TestCase < Test::Unit::TestCase
#puts "select2 - #{params.inspect}"
end
sleep 0.4
screenshot(browser: instance, comment: 'select_after')
end
=begin
@ -695,6 +713,7 @@ class TestCase < Test::Unit::TestCase
log('switch', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'switch_before')
element = instance.find_elements(css: "#{params[:css]} input[type=checkbox]")[0]
checked = element.attribute('checked')
@ -720,6 +739,7 @@ class TestCase < Test::Unit::TestCase
raise 'Switch not off!' if checked
end
end
screenshot(browser: instance, comment: 'switch_after')
end
=begin
@ -736,11 +756,13 @@ class TestCase < Test::Unit::TestCase
log('check', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'check_before')
instance.execute_script("if (!$('#{params[:css]}').prop('checked')) { $('#{params[:css]}').click() }")
#element = instance.find_elements(css: params[:css])[0]
#checked = element.attribute('checked')
#element.click if !checked
screenshot(browser: instance, comment: 'check_after')
end
=begin
@ -757,11 +779,13 @@ class TestCase < Test::Unit::TestCase
log('uncheck', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'uncheck_before')
instance.execute_script("if ($('#{params[:css]}').prop('checked')) { $('#{params[:css]}').click() }")
#element = instance.find_elements(css: params[:css])[0]
#checked = element.attribute('checked')
#element.click if checked
screenshot(browser: instance, comment: 'uncheck_after')
end
=begin
@ -779,10 +803,12 @@ class TestCase < Test::Unit::TestCase
log('sendkey', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'sendkey_before')
if params[:value].class == Array
params[:value].each { |key|
instance.action.send_keys(key).perform
}
screenshot(browser: instance, comment: 'sendkey_after')
return
end
instance.action.send_keys(params[:value]).perform
@ -791,6 +817,7 @@ class TestCase < Test::Unit::TestCase
else
sleep 0.2
end
screenshot(browser: instance, comment: 'sendkey_after')
end
=begin
@ -825,9 +852,11 @@ class TestCase < Test::Unit::TestCase
end
if params[:should_not_match]
if success
screenshot(browser: instance, comment: 'match_failed')
raise "should not match '#{params[:value]}' in select list, but is matching"
end
elsif !success
screenshot(browser: instance, comment: 'match_failed')
raise "not matching '#{params[:value]}' in select list"
end
@ -871,9 +900,11 @@ class TestCase < Test::Unit::TestCase
if match
if params[:should_not_match]
screenshot(browser: instance, comment: 'match_failed')
raise "matching '#{params[:value]}' in content '#{text}' but should not!"
end
elsif !params[:should_not_match]
screenshot(browser: instance, comment: 'match_failed')
raise "not matching '#{params[:value]}' in content '#{text}' but should!"
end
sleep 0.2
@ -1032,10 +1063,11 @@ class TestCase < Test::Unit::TestCase
if title =~ /#{data[:title]}/i
assert(true, "matching '#{data[:title]}' in title '#{title}'")
else
screenshot(browser: instance, comment: 'verify_task_failed')
raise "not matching '#{data[:title]}' in title '#{title}'"
end
end
puts "tv #{params.inspect}"
# verify modified
if data.key?(:modified)
exists = instance.find_elements(css: '.tasks .is-active')[0]
@ -1051,15 +1083,19 @@ class TestCase < Test::Unit::TestCase
if is_modified
assert(true, "task '#{data[:title]}' is modifed")
elsif !exists
screenshot(browser: instance, comment: 'verify_task_failed')
raise "task '#{data[:title]}' not exists, should not modified"
else
screenshot(browser: instance, comment: 'verify_task_failed')
raise "task '#{data[:title]}' is not modifed"
end
elsif !is_modified
assert(true, "task '#{data[:title]}' is modifed")
elsif !exists
screenshot(browser: instance, comment: 'verify_task_failed')
raise "task '#{data[:title]}' not exists, should be not modified"
else
screenshot(browser: instance, comment: 'verify_task_failed')
raise "task '#{data[:title]}' is modifed, but should not"
end
end
@ -1285,12 +1321,14 @@ wait untill text in selector disabppears
switch_window_focus(params)
log('shortcut', params)
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'shortcut_before')
instance.action.key_down(:control)
.key_down(:alt)
.send_keys(params[:key])
.key_up(:alt)
.key_up(:control)
.perform
screenshot(browser: instance, comment: 'shortcut_after')
end
=begin
@ -1829,14 +1867,6 @@ wait untill text in selector disabppears
clear: true,
mute_log: true,
)
# it's not working stable via selenium, use js
value = instance.find_elements(css: '.content .newTicket div[data-name=body]')[0].text
#puts "V #{value.inspect}"
if value != data[:body]
body_quoted = quote(data[:body])
instance.execute_script("$('.content.active div[data-name=body]').html('#{body_quoted}').trigger('focusout')")
end
end
if data[:customer]
element = instance.find_elements(css: '.active .newTicket input[name="customer_id_completion"]')[0]
@ -2260,12 +2290,14 @@ wait untill text in selector disabppears
browser: instance,
js: '$(".content.active .sidebar").css("display", "block")',
)
screenshot(browser: instance, comment: 'ticket_open_by_overview')
instance.find_elements(css: ".content.active .sidebar a[href=\"#{params[:link]}\"]")[0].click
sleep 1
execute(
browser: instance,
js: '$(".content.active .sidebar").css("display", "none")',
)
screenshot(browser: instance, comment: 'ticket_open_by_overview_search')
instance.find_elements(partial_link_text: params[:number])[0].click
sleep 1
number = instance.find_elements(css: '.active .ticketZoom-header .ticket-number')[0].text
@ -2310,8 +2342,9 @@ wait untill text in selector disabppears
sleep 1
# open ticket
screenshot(browser: instance, comment: 'ticket_open_by_search')
#instance.find_element(partial_link_text: params[:number] } ).click
instance.execute_script("$(\".js-global-search-result a:contains('#{params[:value]}') .nav-tab-icon\").click()")
instance.execute_script("$(\".js-global-search-result a:contains('#{params[:number]}') .nav-tab-icon\").first().click()")
sleep 1
number = instance.find_elements(css: '.active .ticketZoom-header .ticket-number')[0].text
if number !~ /#{params[:number]}/
@ -2345,6 +2378,7 @@ wait untill text in selector disabppears
sleep 3
# open ticket
screenshot(browser: instance, comment: 'ticket_open_by_title_search')
#instance.find_element(partial_link_text: params[:title] } ).click
instance.execute_script("$(\".js-global-search-result a:contains('#{params[:title]}') .nav-tab-icon\").click()")
sleep 1
@ -2466,6 +2500,8 @@ wait untill text in selector disabppears
element.clear
element.send_keys(params[:value])
sleep 3
screenshot(browser: instance, comment: 'user_open_by_search')
#instance.find_element(partial_link_text: params[:value]).click
instance.execute_script("$(\".js-global-search-result a:contains('#{params[:value]}') .nav-tab-icon\").click()")
sleep 1