Merge pull request #763 from warpc/759-stop-and-delete-action-for-build-list

[refs #759]: cancel ProductBuildList and BuildList;
refactroing ProductBuildList; add new user role "tester"; set timeout for workers to 12 hours; refactoring worker_observers
This commit is contained in:
Vladimir Sharshov 2012-12-07 03:58:57 -08:00
commit c677e37c20
29 changed files with 326 additions and 135 deletions

View File

@ -57,7 +57,7 @@ class Admin::UsersController < Admin::BaseController
@users = @users.where('users.name ILIKE ? or users.uname ILIKE ? or users.email ILIKE ?', search, search, search) @users = @users.where('users.name ILIKE ? or users.uname ILIKE ? or users.email ILIKE ?', search, search, search)
end end
@filter = params[:filter] || 'all' @filter = params[:filter] || 'all'
@users = @users.send(@filter) if ['real', 'admin', 'banned'].include? @filter @users = @users.send(@filter) if ['real', 'admin', 'banned', 'tester'].include? @filter
@users = @users.order(order) @users = @users.order(order)
render :partial => 'users_ajax', :layout => false render :partial => 'users_ajax', :layout => false

View File

@ -5,7 +5,7 @@ class Platforms::ProductBuildListsController < Platforms::BaseController
load_and_authorize_resource :platform, :except => [:index, :status_build] load_and_authorize_resource :platform, :except => [:index, :status_build]
load_and_authorize_resource :product, :through => :platform, :except => [:index, :status_build] load_and_authorize_resource :product, :through => :platform, :except => [:index, :status_build]
load_and_authorize_resource :product_build_list, :through => :product, :except => [:index, :status_build] load_and_authorize_resource :product_build_list, :through => :product, :except => [:index, :status_build]
load_and_authorize_resource :only => [:index, :show, :log, :stop] load_and_authorize_resource :only => [:index, :show, :log, :cancel]
before_filter :authenticate_product_builder!, :only => [:status_build] before_filter :authenticate_product_builder!, :only => [:status_build]
before_filter :find_product_build_list, :only => [:status_build] before_filter :find_product_build_list, :only => [:status_build]
@ -25,15 +25,18 @@ class Platforms::ProductBuildListsController < Platforms::BaseController
def show def show
end end
def stop def cancel
@product_build_list.stop if @product_build_list.cancel
flash[:notice] = t('flash.product_build_list.will_be_stopped') notice = t('layout.build_lists.will_be_canceled')
redirect_to platform_product_product_build_list_path(@platform, @product, @product_build_list) else
notice = t('layout.build_lists.cancel_fail')
end
redirect_to :back, :notice => notice
end end
def log def log
render :json => { render :json => {
:log => @product_build_list.log, :log => @product_build_list.abf_worker_log,
:building => @product_build_list.build_started? :building => @product_build_list.build_started?
} }
end end

View File

@ -55,7 +55,7 @@ class Projects::BuildListsController < Projects::BaseController
build_for_platforms = Repository.select(:platform_id). build_for_platforms = Repository.select(:platform_id).
where(:id => params[:build_list][:include_repos]).group(:platform_id).map(&:platform_id) where(:id => params[:build_list][:include_repos]).group(:platform_id).map(&:platform_id)
new_core = current_user.admin? && params[:build_list][:new_core] == '1' new_core = BuildList.has_access_to_new_core?(current_user) && params[:build_list][:new_core] == '1'
params[:build_list][:auto_publish] = false if new_core params[:build_list][:auto_publish] = false if new_core
Arch.where(:id => params[:arches]).each do |arch| Arch.where(:id => params[:arches]).each do |arch|
Platform.main.where(:id => build_for_platforms).each do |build_for_platform| Platform.main.where(:id => build_for_platforms).each do |build_for_platform|
@ -101,10 +101,13 @@ class Projects::BuildListsController < Projects::BaseController
def cancel def cancel
if @build_list.cancel if @build_list.cancel
redirect_to :back, :notice => t('layout.build_lists.cancel_success') notice = @build_list.new_core? ?
t('layout.build_lists.will_be_canceled') :
t('layout.build_lists.cancel_success')
else else
redirect_to :back, :notice => t('layout.build_lists.cancel_fail') notice = t('layout.build_lists.cancel_fail')
end end
redirect_to :back, :notice => notice
end end
def log def log

View File

@ -114,7 +114,7 @@ class Ability
can(:read, Product, read_relations_for('products', 'platforms')) {|product| product.platform.main?} can(:read, Product, read_relations_for('products', 'platforms')) {|product| product.platform.main?}
can([:create, :update, :destroy, :clone], Product) {|product| local_admin? product.platform and product.platform.main?} can([:create, :update, :destroy, :clone], Product) {|product| local_admin? product.platform and product.platform.main?}
can([:create, :stop], ProductBuildList) {|pbl| can?(:update, pbl.product)} can([:create, :cancel], ProductBuildList) {|pbl| can?(:update, pbl.product)}
can(:destroy, ProductBuildList) {|pbl| can?(:destroy, pbl.product)} can(:destroy, ProductBuildList) {|pbl| can?(:destroy, pbl.product)}
can [:read, :create], PrivateUser, :platform => {:owner_type => 'User', :owner_id => user.id} can [:read, :create], PrivateUser, :platform => {:owner_type => 'User', :owner_id => user.id}

View File

@ -1,6 +1,7 @@
# -*- encoding : utf-8 -*- # -*- encoding : utf-8 -*-
class BuildList < ActiveRecord::Base class BuildList < ActiveRecord::Base
include Modules::Models::CommitAndVersion include Modules::Models::CommitAndVersion
include AbfWorker::ModelHelper
belongs_to :project belongs_to :project
belongs_to :arch belongs_to :arch
@ -49,11 +50,13 @@ class BuildList < ActiveRecord::Base
BUILD_PUBLISH = 7000 BUILD_PUBLISH = 7000
FAILED_PUBLISH = 8000 FAILED_PUBLISH = 8000
REJECTED_PUBLISH = 9000 REJECTED_PUBLISH = 9000
BUILD_CANCELING = 10000
STATUSES = [ WAITING_FOR_RESPONSE, STATUSES = [ WAITING_FOR_RESPONSE,
BUILD_CANCELED, BUILD_CANCELED,
BUILD_PENDING, BUILD_PENDING,
BUILD_PUBLISHED, BUILD_PUBLISHED,
BUILD_CANCELING,
BUILD_PUBLISH, BUILD_PUBLISH,
FAILED_PUBLISH, FAILED_PUBLISH,
REJECTED_PUBLISH, REJECTED_PUBLISH,
@ -70,6 +73,7 @@ class BuildList < ActiveRecord::Base
HUMAN_STATUSES = { WAITING_FOR_RESPONSE => :waiting_for_response, HUMAN_STATUSES = { WAITING_FOR_RESPONSE => :waiting_for_response,
BUILD_CANCELED => :build_canceled, BUILD_CANCELED => :build_canceled,
BUILD_CANCELING => :build_canceling,
BUILD_PENDING => :build_pending, BUILD_PENDING => :build_pending,
BUILD_PUBLISHED => :build_published, BUILD_PUBLISHED => :build_published,
BUILD_PUBLISH => :build_publish, BUILD_PUBLISH => :build_publish,
@ -137,6 +141,8 @@ class BuildList < ActiveRecord::Base
end end
after_transition :on => :published, :do => [:set_version_and_tag, :actualize_packages] after_transition :on => :published, :do => [:set_version_and_tag, :actualize_packages]
after_transition :on => :cancel, :do => [:cancel_job],
:if => lambda { |build_list| build_list.new_core? }
after_transition :on => [:published, :fail_publish, :build_error], :do => :notify_users after_transition :on => [:published, :fail_publish, :build_error], :do => :notify_users
after_transition :on => :build_success, :do => :notify_users, after_transition :on => :build_success, :do => :notify_users,
@ -169,7 +175,18 @@ class BuildList < ActiveRecord::Base
event :cancel do event :cancel do
transition [:build_pending, :platform_pending] => :build_canceled, :if => lambda { |build_list| transition [:build_pending, :platform_pending] => :build_canceled, :if => lambda { |build_list|
build_list.can_cancel? && BuildServer.delete_build_list(build_list.bs_id) == BuildServer::SUCCESS !build_list.new_core? && build_list.can_cancel? && BuildServer.delete_build_list(build_list.bs_id) == BuildServer::SUCCESS
}
transition [:build_pending, :build_started] => :build_canceling, :if => lambda { |build_list|
build_list.new_core?
}
end
# :build_canceling => :build_canceled - canceling from UI
# :build_started => :build_canceled - canceling from worker by time-out (time_living has been expired)
event :build_canceled do
transition [:build_canceling, :build_started] => :build_canceled, :if => lambda { |build_list|
build_list.new_core?
} }
end end
@ -197,7 +214,7 @@ class BuildList < ActiveRecord::Base
end end
event :build_error do event :build_error do
transition [:build_started, :build_canceled] => :build_error transition [:build_started, :build_canceled, :build_canceling] => :build_error
end end
HUMAN_STATUSES.each do |code,name| HUMAN_STATUSES.each do |code,name|
@ -226,8 +243,12 @@ class BuildList < ActiveRecord::Base
#TODO: Share this checking on product owner. #TODO: Share this checking on product owner.
def can_cancel? def can_cancel?
if new_core?
build_started? || build_pending?
else
[BUILD_PENDING, BuildServer::PLATFORM_PENDING].include?(status) && bs_id [BUILD_PENDING, BuildServer::PLATFORM_PENDING].include?(status) && bs_id
end end
end
def can_publish? def can_publish?
[BuildServer::SUCCESS, FAILED_PUBLISH].include? status [BuildServer::SUCCESS, FAILED_PUBLISH].include? status
@ -237,60 +258,11 @@ class BuildList < ActiveRecord::Base
can_publish? and not save_to_repository.publish_without_qa can_publish? and not save_to_repository.publish_without_qa
end end
def add_to_abf_worker_queue
include_repos_hash = {}.tap do |h|
include_repos.each do |r|
repo = Repository.find r
path = repo.platform.public_downloads_url(nil, arch.name, repo.name)
# path = path.gsub(/^http:\/\/0\.0\.0\.0\:3000/, 'https://abf.rosalinux.ru')
# Path looks like:
# http://abf.rosalinux.ru/downloads/rosa-server2012/repository/x86_64/base/
# so, we should append:
# /release
# /updates
h["#{repo.name}_release"] = path + 'release'
h["#{repo.name}_updates"] = path + 'updates'
end
end
# mdv example:
# https://abf.rosalinux.ru/import/plasma-applet-stackfolder.git
# bfe6d68cc607238011a6108014bdcfe86c69456a
# rhel example:
# https://abf.rosalinux.ru/server/gnome-settings-daemon.git
# fbb2549e44d97226fea6748a4f95d1d82ffb8726
options = {
:id => id,
:arch => arch.name,
:time_living => 2880, # 2 days
:distrib_type => build_for_platform.distrib_type,
# :git_project_address => 'https://abf.rosalinux.ru/server/gnome-settings-daemon.git',
:git_project_address => project.git_project_address,
# :commit_hash => 'fbb2549e44d97226fea6748a4f95d1d82ffb8726',
:commit_hash => commit_hash,
:build_requires => build_requires,
:include_repos => include_repos_hash,
:bplname => build_for_platform.name
# :project_version => project_version,
# :plname => save_to_platform.name,
# :bplname => (save_to_platform_id == build_for_platform_id ? '' : build_for_platform.name),
# :update_type => update_type,
# :priority => priority,
}
unless @status
Resque.push(
'rpm_worker',
'class' => 'AbfWorker::RpmWorker',
'args' => [options]
)
end
@status ||= BUILD_PENDING
end
def add_to_queue def add_to_queue
if new_core? if new_core?
add_to_abf_worker_queue # TODO: Investigate: why 2 tasks will be created without checking @state
add_job_to_abf_worker_queue unless @status
@status ||= BUILD_PENDING
else else
# XML-RPC params: # XML-RPC params:
# - project_name # - project_name
@ -321,6 +293,10 @@ class BuildList < ActiveRecord::Base
@status @status
end end
def self.has_access_to_new_core?(user)
user && (user.admin? || user.tester?)
end
def self.human_status(status) def self.human_status(status)
I18n.t("layout.build_lists.statuses.#{HUMAN_STATUSES[status]}") I18n.t("layout.build_lists.statuses.#{HUMAN_STATUSES[status]}")
end end
@ -390,16 +366,54 @@ class BuildList < ActiveRecord::Base
def log(load_lines) def log(load_lines)
if new_core? if new_core?
log = Resque.redis.get("abfworker::rpm-worker-#{id}") abf_worker_log
else else
log = `tail -n #{load_lines.to_i} #{Rails.root + 'public' + fs_log_path}` log = `tail -n #{load_lines.to_i} #{Rails.root + 'public' + fs_log_path}`
log = nil unless $?.success? $?.success? ? log : I18n.t('layout.build_lists.log.not_available')
end end
log || I18n.t('layout.build_lists.log.not_available')
end end
protected protected
def abf_worker_args
include_repos_hash = {}.tap do |h|
include_repos.each do |r|
repo = Repository.find r
path = repo.platform.public_downloads_url(nil, arch.name, repo.name)
# path = path.gsub(/^http:\/\/0\.0\.0\.0\:3000/, 'https://abf.rosalinux.ru')
# Path looks like:
# http://abf.rosalinux.ru/downloads/rosa-server2012/repository/x86_64/base/
# so, we should append:
# /release
# /updates
h["#{repo.name}_release"] = path + 'release'
h["#{repo.name}_updates"] = path + 'updates'
end
end
# mdv example:
# https://abf.rosalinux.ru/import/plasma-applet-stackfolder.git
# bfe6d68cc607238011a6108014bdcfe86c69456a
# rhel example:
# https://abf.rosalinux.ru/server/gnome-settings-daemon.git
# fbb2549e44d97226fea6748a4f95d1d82ffb8726
{
:id => id,
:arch => arch.name,
:time_living => 43200, # 12 hours
:distrib_type => build_for_platform.distrib_type,
# :git_project_address => 'https://abf.rosalinux.ru/server/gnome-settings-daemon.git',
:git_project_address => project.git_project_address,
# :commit_hash => 'fbb2549e44d97226fea6748a4f95d1d82ffb8726',
:commit_hash => commit_hash,
:build_requires => build_requires,
:include_repos => include_repos_hash,
:bplname => build_for_platform.name,
:user => {:uname => user.uname, :email => user.email}
}
end
def notify_users def notify_users
unless mass_build_id unless mass_build_id
users = [] users = []

View File

@ -7,14 +7,15 @@ class BuildList::Item < ActiveRecord::Base
GIT_ERROR = 5 GIT_ERROR = 5
STATUSES = [BuildServer::SUCCESS, BuildServer::DEPENDENCIES_ERROR, BuildServer::BUILD_ERROR, BuildServer::BUILD_STARTED, GIT_ERROR] STATUSES = [BuildServer::SUCCESS, BuildServer::DEPENDENCIES_ERROR, BuildServer::BUILD_ERROR, BuildServer::BUILD_STARTED, GIT_ERROR, BuildList::BUILD_CANCELED]
HUMAN_STATUSES = { HUMAN_STATUSES = {
nil => :unknown, nil => :unknown,
GIT_ERROR => :git_error, GIT_ERROR => :git_error,
BuildServer::DEPENDENCIES_ERROR => :dependencies_error, BuildServer::DEPENDENCIES_ERROR => :dependencies_error,
BuildServer::SUCCESS => :success, BuildServer::SUCCESS => :success,
BuildServer::BUILD_STARTED => :build_started, BuildServer::BUILD_STARTED => :build_started,
BuildServer::BUILD_ERROR => :build_error BuildServer::BUILD_ERROR => :build_error,
BuildList::BUILD_CANCELED => :build_canceled
} }
scope :recent, order("level ASC, name ASC") scope :recent, order("level ASC, name ASC")

View File

@ -4,9 +4,12 @@ class BuildListObserver < ActiveRecord::Observer
def before_update(record) def before_update(record)
if record.status_changed? if record.status_changed?
record.started_at = Time.now if record.status == BuildServer::BUILD_STARTED record.started_at = Time.now if record.status == BuildServer::BUILD_STARTED
if [BuildServer::BUILD_ERROR, BuildServer::SUCCESS].include? record.status if [BuildServer::BUILD_ERROR,
BuildServer::SUCCESS,
BuildList::BUILD_CANCELING,
BuildList::BUILD_CANCELED].include? record.status
# stores time interval beetwin build start and finish in seconds # stores time interval beetwin build start and finish in seconds
record.duration = record.current_duration record.duration = record.current_duration if record.started_at
if record.status == BuildServer::SUCCESS if record.status == BuildServer::SUCCESS
# Update project average build time # Update project average build time

View File

@ -1,5 +1,7 @@
# -*- encoding : utf-8 -*- # -*- encoding : utf-8 -*-
class Product < ActiveRecord::Base class Product < ActiveRecord::Base
include Modules::Models::TimeLiving
belongs_to :platform belongs_to :platform
belongs_to :project belongs_to :project
has_many :product_build_lists, :dependent => :destroy has_many :product_build_lists, :dependent => :destroy
@ -13,8 +15,7 @@ class Product < ActiveRecord::Base
:description, :description,
:project_id, :project_id,
:main_script, :main_script,
:params, :params
:time_living
attr_readonly :platform_id attr_readonly :platform_id
def full_clone(attrs = {}) def full_clone(attrs = {})

View File

@ -1,6 +1,8 @@
# -*- encoding : utf-8 -*- # -*- encoding : utf-8 -*-
class ProductBuildList < ActiveRecord::Base class ProductBuildList < ActiveRecord::Base
include Modules::Models::CommitAndVersion include Modules::Models::CommitAndVersion
include Modules::Models::TimeLiving
include AbfWorker::ModelHelper
delegate :url_helpers, to: 'Rails.application.routes' delegate :url_helpers, to: 'Rails.application.routes'
BUILD_COMPLETED = 0 BUILD_COMPLETED = 0
@ -35,7 +37,6 @@ class ProductBuildList < ActiveRecord::Base
:status, :status,
:project_id, :project_id,
:main_script, :main_script,
:time_living,
:arch_id, :presence => true :arch_id, :presence => true
validates :status, :inclusion => { :in => STATUSES } validates :status, :inclusion => { :in => STATUSES }
@ -48,7 +49,6 @@ class ProductBuildList < ActiveRecord::Base
:params, :params,
:project_version, :project_version,
:commit_hash, :commit_hash,
:time_living,
:arch_id :arch_id
attr_readonly :product_id attr_readonly :product_id
serialize :results, Array serialize :results, Array
@ -60,10 +60,40 @@ class ProductBuildList < ActiveRecord::Base
scope :scoped_to_product_name, lambda {|product_name| joins(:product).where('products.name LIKE ?', "%#{product_name}%")} scope :scoped_to_product_name, lambda {|product_name| joins(:product).where('products.name LIKE ?', "%#{product_name}%")}
scope :recent, order("#{table_name}.updated_at DESC") scope :recent, order("#{table_name}.updated_at DESC")
after_create :xml_rpc_create after_create :add_job_to_abf_worker_queue
before_destroy :can_destroy? before_destroy :can_destroy?
after_destroy :xml_delete_iso_container after_destroy :xml_delete_iso_container
state_machine :status, :initial => :build_pending do
event :start_build do
transition :build_pending => :build_started
end
event :cancel do
transition [:build_pending, :build_started] => :build_canceling
end
after_transition :on => :cancel, :do => [:cancel_job]
# :build_canceling => :build_canceled - canceling from UI
# :build_started => :build_canceled - canceling from worker by time-out (time_living has been expired)
event :build_canceled do
transition [:build_canceling, :build_started] => :build_canceled
end
event :build_success do
transition :build_started => :build_completed
end
event :build_error do
transition [:build_started, :build_canceled, :build_canceling] => :build_failed
end
HUMAN_STATUSES.each do |code,name|
state name, :value => code
end
end
def build_started? def build_started?
status == BUILD_STARTED status == BUILD_STARTED
end end
@ -72,6 +102,10 @@ class ProductBuildList < ActiveRecord::Base
status == BUILD_CANCELING status == BUILD_CANCELING
end end
def can_cancel?
[BUILD_STARTED, BUILD_PENDING].include? status
end
def container_path def container_path
"/downloads/#{product.platform.name}/product/#{id}/" "/downloads/#{product.platform.name}/product/#{id}/"
end end
@ -92,22 +126,9 @@ class ProductBuildList < ActiveRecord::Base
[BUILD_COMPLETED, BUILD_FAILED, BUILD_CANCELED].include? status [BUILD_COMPLETED, BUILD_FAILED, BUILD_CANCELED].include? status
end end
def log
Resque.redis.get("abfworker::iso-worker-#{id}") || ''
end
def stop
update_attributes({:status => BUILD_CANCELING})
Resque.redis.setex(
"abfworker::iso-worker-#{id}::live-inspector",
120, # Data will be removed from Redis after 120 sec.
'USR1' # Immediately kill child but don't exit
)
end
protected protected
def xml_rpc_create def abf_worker_args
file_name = "#{project.owner.uname}-#{project.name}-#{commit_hash}" file_name = "#{project.owner.uname}-#{project.name}-#{commit_hash}"
srcpath = url_helpers.archive_url( srcpath = url_helpers.archive_url(
project.owner, project.owner,
@ -116,7 +137,7 @@ class ProductBuildList < ActiveRecord::Base
'tar.gz', 'tar.gz',
:host => ActionMailer::Base.default_url_options[:host] :host => ActionMailer::Base.default_url_options[:host]
) )
options = { {
:id => id, :id => id,
# TODO: remove comment # TODO: remove comment
# :srcpath => 'http://dl.dropbox.com/u/945501/avokhmin-test-iso-script-5d9b463d4e9c06ea8e7c89e1b7ff5cb37e99e27f.tar.gz', # :srcpath => 'http://dl.dropbox.com/u/945501/avokhmin-test-iso-script-5d9b463d4e9c06ea8e7c89e1b7ff5cb37e99e27f.tar.gz',
@ -127,12 +148,6 @@ class ProductBuildList < ActiveRecord::Base
:arch => arch.name, :arch => arch.name,
:distrib_type => product.platform.distrib_type :distrib_type => product.platform.distrib_type
} }
Resque.push(
'iso_worker',
'class' => 'AbfWorker::IsoWorker',
'args' => [options]
)
return true
end end
def xml_delete_iso_container def xml_delete_iso_container

View File

@ -1,6 +1,6 @@
# -*- encoding : utf-8 -*- # -*- encoding : utf-8 -*-
class User < Avatar class User < Avatar
ROLES = ['', 'admin', 'banned'] ROLES = ['', 'admin', 'banned', 'tester']
LANGUAGES_FOR_SELECT = [['Russian', 'ru'], ['English', 'en']] LANGUAGES_FOR_SELECT = [['Russian', 'ru'], ['English', 'en']]
LANGUAGES = LANGUAGES_FOR_SELECT.map(&:last) LANGUAGES = LANGUAGES_FOR_SELECT.map(&:last)
@ -42,6 +42,7 @@ class User < Avatar
scope :opened, where('1=1') scope :opened, where('1=1')
scope :banned, where(:role => 'banned') scope :banned, where(:role => 'banned')
scope :admin, where(:role => 'admin') scope :admin, where(:role => 'admin')
scope :tester, where(:role => 'tester')
scope :real, where(:role => ['', nil]) scope :real, where(:role => ['', nil])
scope :member_of_project, lambda {|item| scope :member_of_project, lambda {|item|
@ -66,6 +67,10 @@ class User < Avatar
new_record? new_record?
end end
def tester?
role == 'tester'
end
def access_locked? def access_locked?
role == 'banned' role == 'banned'
end end

View File

@ -18,16 +18,19 @@
= render 'show_field', :key => :project_version, :value => product_build_list_version_link(pbl, true) = render 'show_field', :key => :project_version, :value => product_build_list_version_link(pbl, true)
= render 'show_field', :key => :arch, :value => pbl.arch.name = render 'show_field', :key => :arch, :value => pbl.arch.name
- [:main_script, :params, :time_living].each do |el| - [:main_script, :params].each do |el|
= render 'show_field', :key => el, :value => pbl.send(el) = render 'show_field', :key => el, :value => pbl.send(el)
= render 'show_field', :key => :time_living, :value => (pbl.time_living / 60)
= render 'show_field', :key => :notified_at, :value => l(pbl.updated_at, :format => :long) = render 'show_field', :key => :notified_at, :value => l(pbl.updated_at, :format => :long)
- if pbl.build_started? || pbl.build_canceling? - if pbl.can_cancel? && can?(:cancel, pbl)
- if can? :stop, pbl = link_to t("layout.build_lists.cancel"), cancel_platform_product_product_build_list_path(pbl.product.platform, pbl.product, pbl),
.leftlist= t("layout.product_build_lists.action") :method => :put, :confirm => t("layout.confirm"), :class => 'button'
.rightlist= link_to t("layout.product_build_lists.stop"), stop_platform_product_product_build_list_path(pbl.product.platform, pbl.product, pbl)
.both .both
- if pbl.build_started? || pbl.build_canceling?
= render 'shared/log', { :build_started => true, :get_log_path => log_platform_product_product_build_list_path(pbl.product.platform, pbl.product, pbl) } = render 'shared/log', { :build_started => true, :get_log_path => log_platform_product_product_build_list_path(pbl.product.platform, pbl.product, pbl) }
= render 'results', :pbl => pbl = render 'results', :pbl => pbl

View File

@ -1,4 +1,8 @@
- [:main_script, :params, :time_living].each do |el| - [:main_script, :params].each do |el|
.leftlist= f.label el, t("activerecord.attributes.product_build_list.#{el}"), :class => :label .leftlist= f.label el, t("activerecord.attributes.product_build_list.#{el}"), :class => :label
.rightlist= f.text_field el .rightlist= f.text_field el
.both .both
.leftlist= f.label :time_living, t("activerecord.attributes.product_build_list.time_living"), :class => :label
.rightlist= f.text_field :time_living, :value => (@product.time_living / 60)
.both

View File

@ -29,7 +29,7 @@
.both .both
= f.check_box :build_requires = f.check_box :build_requires
= f.label :build_requires = f.label :build_requires
- if current_user.admin? - if BuildList.has_access_to_new_core?(current_user)
.both .both
= f.check_box :new_core = f.check_box :new_core
= f.label :new_core = f.label :new_core

View File

@ -62,6 +62,7 @@ en:
show: Show show: Show
cancel: Cancel build cancel: Cancel build
cancel_success: 'Build canceled' cancel_success: 'Build canceled'
will_be_canceled: 'Build will be canceled...'
publish_success: 'Build published' publish_success: 'Build published'
cancel_fail: 'Errors during build cancelation!' cancel_fail: 'Errors during build cancelation!'
publish_fail: 'Errors during build publishing!' publish_fail: 'Errors during build publishing!'
@ -100,6 +101,7 @@ en:
success: Build complete success: Build complete
unknown: Build is waiting unknown: Build is waiting
git_error: Git error git_error: Git error
build_canceled: Build canceled
statuses: statuses:
build_lists: All build_lists: All
@ -111,6 +113,7 @@ en:
dependencies_fail: Dependences not found dependencies_fail: Dependences not found
waiting_for_response: Waiting for response waiting_for_response: Waiting for response
build_pending: Build pending build_pending: Build pending
build_canceling: Build is canceling
dependency_test_failed: Dependency test failed dependency_test_failed: Dependency test failed
binary_test_failed: Binary test failed binary_test_failed: Binary test failed
build_canceled: Build canceled build_canceled: Build canceled

View File

@ -60,6 +60,7 @@ ru:
no_items_data: Данных нет no_items_data: Данных нет
show: Просмотр show: Просмотр
cancel: Отменить сборку cancel: Отменить сборку
will_be_canceled: 'Сборка будет отменена...'
cancel_success: 'Сборка отменена.' cancel_success: 'Сборка отменена.'
cancel_fail: 'При отмене сборки произошла ошибка!' cancel_fail: 'При отмене сборки произошла ошибка!'
publish_success: 'Сборка поставлена в очередь на публикацию.' publish_success: 'Сборка поставлена в очередь на публикацию.'
@ -97,6 +98,7 @@ ru:
success: собран success: собран
unknown: ожидает сборки unknown: ожидает сборки
git_error: проблема с гит git_error: проблема с гит
build_canceled: сборка отменена
statuses: statuses:
build_lists: Всего build_lists: Всего
@ -111,6 +113,7 @@ ru:
dependency_test_failed: тестирование зависимостей не пройдено dependency_test_failed: тестирование зависимостей не пройдено
binary_test_failed: тестирование бинарной совместимости не пройдено binary_test_failed: тестирование бинарной совместимости не пройдено
build_canceled: сборка отменена build_canceled: сборка отменена
build_canceling: сборка отменяется
success: собран success: собран
build_started: собирается build_started: собирается
platform_not_found: платформа не найдена platform_not_found: платформа не найдена

View File

@ -32,3 +32,4 @@ en:
created_at: Created created_at: Created
updated_at: Updated updated_at: Updated
project: Project project: Project
time_living: Max time for building (in minutes)

View File

@ -32,3 +32,4 @@ ru:
created_at: Создан created_at: Создан
updated_at: Обновлен updated_at: Обновлен
project: Проект project: Проект
time_living: Максимальное время сборки (в минутах)

View File

@ -8,7 +8,6 @@ en:
results_folder: all files from this folder will be uploaded into the file-store results_folder: all files from this folder will be uploaded into the file-store
archives_folder: this folder will be archived and uploaded into the file-store archives_folder: this folder will be archived and uploaded into the file-store
delete: Delete delete: Delete
stop: Stop
action: Action action: Action
id_search: 'Id search' id_search: 'Id search'
new: New build new: New build
@ -53,4 +52,3 @@ en:
no_project: Project for build should be exist no_project: Project for build should be exist
delete: Product build list deleted delete: Product build list deleted
delete_error: Unable to delete product build list delete_error: Unable to delete product build list
will_be_stopped: Build will be stopped

View File

@ -8,7 +8,6 @@ ru:
results_folder: все файлы из этой папки будут загружены на file-store results_folder: все файлы из этой папки будут загружены на file-store
archives_folder: эта папка будет заархивирована и загружена на file-store archives_folder: эта папка будет заархивирована и загружена на file-store
delete: Удалить delete: Удалить
stop: Остановить
action: Действие action: Действие
id_search: 'Поиск по Id' id_search: 'Поиск по Id'
new: Новая сборка new: Новая сборка
@ -53,4 +52,3 @@ ru:
no_project: Проект для сборки должен присутствовать no_project: Проект для сборки должен присутствовать
delete: Сборочный лист продукта удален delete: Сборочный лист продукта удален
delete_error: Не удалось удалить cборочный лист продукта delete_error: Не удалось удалить cборочный лист продукта
will_be_stopped: Сборка будет остановлена

View File

@ -0,0 +1,4 @@
en:
flash:
time_living:
numericality_error: must be 2 to 720 minutes

View File

@ -0,0 +1,4 @@
ru:
flash:
time_living:
numericality_error: должно быть от 2 до 720 минут

View File

@ -26,6 +26,7 @@ en:
admin: Admins admin: Admins
real: Real real: Real
banned: Banned banned: Banned
tester: Testers
register_request: register_request:
list_header: Register requests list_header: Register requests

View File

@ -26,6 +26,7 @@ ru:
admin: Админы admin: Админы
real: Обычные real: Обычные
banned: Забаненные banned: Забаненные
tester: Тестеры
register_request: register_request:
list_header: Заявки на регистрацию list_header: Заявки на регистрацию

View File

@ -151,7 +151,7 @@ Rosa::Application.routes.draw do
resources :product_build_lists, :only => [:create, :destroy, :new, :show] do resources :product_build_lists, :only => [:create, :destroy, :new, :show] do
member { member {
get :log get :log
get :stop put :cancel
} }
end end
collection { get :autocomplete_project } collection { get :autocomplete_project }

View File

@ -1,14 +1,30 @@
module AbfWorker module AbfWorker
class IsoWorkerObserver class IsoWorkerObserver
BUILD_COMPLETED = 0
BUILD_FAILED = 1
BUILD_PENDING = 2
BUILD_STARTED = 3
BUILD_CANCELED = 4
@queue = :iso_worker_observer @queue = :iso_worker_observer
def self.perform(options) def self.perform(options)
status = options['status'].to_i status = options['status'].to_i
pbl = ProductBuildList.find options['id'] pbl = ProductBuildList.find options['id']
pbl.status = status case status
pbl.results = options['results'] if status != ProductBuildList::BUILD_STARTED when BUILD_COMPLETED
pbl.build_success
when BUILD_FAILED
pbl.build_error
when BUILD_STARTED
pbl.start_build
when BUILD_CANCELED
pbl.build_canceled
end
if status != BUILD_STARTED
pbl.results = options['results']
pbl.save! pbl.save!
end end
end
end end
end end

View File

@ -0,0 +1,68 @@
module AbfWorker
module ModelHelper
# In model which contains this helper should be:
# - #abf_worker_args
# - #build_canceled
def abf_worker_log
q = 'abfworker::'
q << worker_queue('-')
q << '-'
q << id.to_s
Resque.redis.get(q) || I18n.t('layout.build_lists.log.not_available')
end
def add_job_to_abf_worker_queue
Resque.push(
worker_queue,
'class' => worker_queue_class,
'args' => [abf_worker_args]
)
end
def cancel_job
deleted = Resque::Job.destroy(
worker_queue,
worker_queue_class,
abf_worker_args
)
if deleted == 1
build_canceled
else
send_stop_signal
end
true
end
private
def send_stop_signal
Resque.redis.setex(
live_inspector_queue,
240, # Data will be removed from Redis after 240 sec.
'USR1' # Immediately kill child but don't exit
)
end
def live_inspector_queue
q = 'abfworker::'
q << worker_queue('-')
q << '-'
q << id.to_s
q << '::live-inspector'
q
end
def worker_queue(delimiter = '_')
a = []
a << (is_a?(BuildList) ? 'rpm' : 'iso')
a << 'worker'
a.join(delimiter)
end
def worker_queue_class
is_a?(BuildList) ? 'AbfWorker::RpmWorker' : 'AbfWorker::IsoWorker'
end
end
end

View File

@ -1,5 +1,11 @@
module AbfWorker module AbfWorker
class RpmWorkerObserver class RpmWorkerObserver
BUILD_COMPLETED = 0
BUILD_FAILED = 1
BUILD_PENDING = 2
BUILD_STARTED = 3
BUILD_CANCELED = 4
@queue = :rpm_worker_observer @queue = :rpm_worker_observer
def self.perform(options) def self.perform(options)
@ -7,18 +13,21 @@ module AbfWorker
status = options['status'].to_i status = options['status'].to_i
item = find_or_create_item(bl) item = find_or_create_item(bl)
case status case status
when 0 when BUILD_COMPLETED
bl.build_success bl.build_success
item.update_attributes({:status => BuildServer::SUCCESS}) item.update_attributes({:status => BuildServer::SUCCESS})
when 1 when BUILD_FAILED
bl.build_error bl.build_error
item.update_attributes({:status => BuildServer::BUILD_ERROR}) item.update_attributes({:status => BuildServer::BUILD_ERROR})
when 3 when BUILD_STARTED
bl.bs_id = bl.id bl.bs_id = bl.id
bl.save! bl.save!
bl.start_build bl.start_build
when BUILD_CANCELED
bl.build_canceled
item.update_attributes({:status => BuildList::BUILD_CANCELED})
end end
if status != 3 if status != BUILD_STARTED
fill_container_data bl, options fill_container_data bl, options
end end
end end

View File

@ -0,0 +1,32 @@
# -*- encoding : utf-8 -*-
module Modules
module Models
module TimeLiving
extend ActiveSupport::Concern
included do
validates :time_living, :numericality => {
:only_integer => true
}, :presence => true
validate lambda {
# 2 min <= time_living <= 12 hours
if 120 > time_living || time_living > 43200
errors.add(:time_living, I18n.t('flash.time_living.numericality_error'))
end
}
before_validation :convert_time_living
attr_accessible :time_living
end
protected
def convert_time_living
self.time_living = time_living.to_i * 60 if time_living_was.to_i != time_living.to_i
end
end
end
end

View File

@ -23,8 +23,8 @@ shared_examples_for 'product build list admin' do
response.should render_template(:index) response.should render_template(:index)
end end
it 'should be able to perform stop action' do it 'should be able to perform cancel action' do
get :stop, valid_attributes_for_show put :cancel, valid_attributes_for_show
response.should redirect_to(platform_product_product_build_list_path(@product.platform, @product, @pbl)) response.should redirect_to(platform_product_product_build_list_path(@product.platform, @product, @pbl))
end end
@ -56,8 +56,8 @@ shared_examples_for 'product build list user without admin rights' do
response.should_not be_success response.should_not be_success
end end
it 'should not be able to perform stop action' do it 'should not be able to perform cancel action' do
get :stop, valid_attributes_for_show put :cancel, valid_attributes_for_show
response.should_not redirect_to(platform_product_product_build_list_path(@product.platform, @product, @pbl)) response.should_not redirect_to(platform_product_product_build_list_path(@product.platform, @product, @pbl))
end end
end end