Added build_completed_partially status for ProductBuildList

This commit is contained in:
Vokhmin Alexey V 2015-03-04 00:04:37 +03:00
parent 264ea371f6
commit 41d72ce87a
11 changed files with 215 additions and 164 deletions

View File

@ -82,7 +82,9 @@ class Platforms::ProductBuildListsController < Platforms::BaseController
scoped_to_product_name(@product_build_list.product_name). scoped_to_product_name(@product_build_list.product_name).
for_status(@product_build_list.status) for_status(@product_build_list.status)
end end
@product_build_lists = @product_build_lists.recent.paginate page: params[:page] @product_build_lists = @product_build_lists.
includes(:project, product: :platform).
recent.paginate(page: params[:page])
@build_server_status = AbfWorkerStatusPresenter.new.products_status @build_server_status = AbfWorkerStatusPresenter.new.products_status
end end

View File

@ -11,7 +11,14 @@ module AbfWorker
when COMPLETED when COMPLETED
subject.build_success subject.build_success
when FAILED when FAILED
subject.build_error
case options['exit_status'].to_i
when ProductBuildList::BUILD_COMPLETED_PARTIALLY
subject.build_success_partially
else
subject.build_error
end
when STARTED when STARTED
subject.start_build subject.start_build
when CANCELED when CANCELED

View File

@ -0,0 +1,69 @@
# Internal: various definitions and instance methods related to AbfWorker.
#
# This module gets mixed in into ProductBuildList class.
module ProductBuildList::AbfWorkerable
extend ActiveSupport::Concern
CACHED_CHROOT_TOKEN_DESCRIPTION = 'cached-chroot'
include AbfWorkerMethods
included do
delegate :url_helpers, to: 'Rails.application.routes'
after_create :add_job_to_abf_worker_queue
end
######################################
# Instance methods #
######################################
def sha1_of_file_store_files
(results || []).map{ |r| r['sha1'] }.compact
end
protected
def abf_worker_priority
''
end
def abf_worker_base_queue
'iso_worker'
end
def abf_worker_args
file_name = "#{project.name}-#{commit_hash}"
opts = default_url_options
opts.merge!({user: user.authentication_token, password: ''}) if user.present?
srcpath = url_helpers.archive_url(
project.name_with_owner,
file_name,
'tar.gz',
opts
)
cmd_params = "BUILD_ID=#{id} "
if product.platform.hidden?
token = product.platform.tokens.by_active.where(description: CACHED_CHROOT_TOKEN_DESCRIPTION).first
cmd_params << "TOKEN=#{token.authentication_token} " if token
end
cmd_params << params.to_s
{
id: id,
srcpath: srcpath,
params: cmd_params,
time_living: time_living,
main_script: main_script,
platform: {
type: product.platform.distrib_type,
name: product.platform.name,
arch: arch.name
},
user: {uname: user.try(:uname), email: user.try(:email)}
}
end
end

View File

@ -0,0 +1,117 @@
# Internal: various definitions and instance methods related to status.
#
# This module gets mixed in into ProductBuildList class.
module ProductBuildList::Statusable
extend ActiveSupport::Concern
BUILD_COMPLETED = 0
BUILD_FAILED = 1
BUILD_PENDING = 2
BUILD_STARTED = 3
BUILD_CANCELED = 4
BUILD_CANCELING = 5
BUILD_COMPLETED_PARTIALLY = 6
STATUSES = [
BUILD_STARTED,
BUILD_COMPLETED,
BUILD_COMPLETED_PARTIALLY,
BUILD_FAILED,
BUILD_PENDING,
BUILD_CANCELED,
BUILD_CANCELING
].freeze
HUMAN_STATUSES = {
BUILD_STARTED => :build_started,
BUILD_COMPLETED => :build_completed,
BUILD_COMPLETED_PARTIALLY => :build_completed_partially,
BUILD_FAILED => :build_failed,
BUILD_PENDING => :build_pending,
BUILD_CANCELED => :build_canceled,
BUILD_CANCELING => :build_canceling
}.freeze
included do
scope :for_status, -> (status) { where(status: status) if status.present? }
validates :status,
presence: true,
inclusion: { in: STATUSES }
attr_accessible :status
before_destroy :can_destroy?
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
# build_canceling: :build_completed - Worker hasn't time to cancel building because build had been already completed
event :build_success do
transition [:build_started, :build_canceling] => :build_completed
end
# build_canceling: :build_completed - Worker hasn't time to cancel building because build had been already completed
event :build_success_partially do
transition [:build_started, :build_canceling] => :build_completed_partially
end
# build_canceling: :build_failed - Worker hasn't time to cancel building because build had been already failed
event :build_error do
transition [:build_started, :build_canceling] => :build_failed
end
HUMAN_STATUSES.each do |code,name|
state name, value: code
end
end
end
module ClassMethods
def human_status(status)
I18n.t("layout.product_build_lists.statuses.#{HUMAN_STATUSES[status]}")
end
end
######################################
# Instance methods #
######################################
def build_started?
status == BUILD_STARTED
end
def build_canceling?
status == BUILD_CANCELING
end
def can_destroy?
[BUILD_STARTED, BUILD_PENDING, BUILD_CANCELING].exclude?(status)
end
def can_cancel?
[BUILD_STARTED, BUILD_PENDING].include?(status)
end
def human_status
self.class.human_status(status)
end
end

View File

@ -3,39 +3,13 @@ class ProductBuildList < ActiveRecord::Base
include TimeLiving include TimeLiving
include FileStoreClean include FileStoreClean
include UrlHelper include UrlHelper
include AbfWorkerMethods
include EventLoggable include EventLoggable
include ProductBuildList::Statusable
delegate :url_helpers, to: 'Rails.application.routes' include ProductBuildList::AbfWorkerable
LIVE_TIME = 2.week # for autostart LIVE_TIME = 2.week # for autostart
MAX_LIVE_TIME = 3.month # for manual start; MAX_LIVE_TIME = 3.month # for manual start;
BUILD_COMPLETED = 0
BUILD_FAILED = 1
BUILD_PENDING = 2
BUILD_STARTED = 3
BUILD_CANCELED = 4
BUILD_CANCELING = 5
STATUSES = [ BUILD_STARTED,
BUILD_COMPLETED,
BUILD_FAILED,
BUILD_PENDING,
BUILD_CANCELED,
BUILD_CANCELING
].freeze
HUMAN_STATUSES = { BUILD_STARTED => :build_started,
BUILD_COMPLETED => :build_completed,
BUILD_FAILED => :build_failed,
BUILD_PENDING => :build_pending,
BUILD_CANCELED => :build_canceled,
BUILD_CANCELING => :build_canceling
}.freeze
CACHED_CHROOT_TOKEN_DESCRIPTION = 'cached-chroot'
belongs_to :product belongs_to :product
belongs_to :project belongs_to :project
belongs_to :arch belongs_to :arch
@ -47,17 +21,14 @@ class ProductBuildList < ActiveRecord::Base
before_validation -> { self.not_delete = false unless build_completed?; true } before_validation -> { self.not_delete = false unless build_completed?; true }
validates :product, :product_id, validates :product, :product_id,
:status,
:project, :project_id, :project, :project_id,
:main_script, :main_script,
:arch, :arch_id, :arch, :arch_id,
presence: true presence: true
validates :status, inclusion: { in: STATUSES }
validates :main_script, :params, length: { maximum: 255 } validates :main_script, :params, length: { maximum: 255 }
attr_accessor :base_url, :product_name attr_accessor :base_url, :product_name
attr_accessible :status, attr_accessible :base_url,
:base_url,
:branch, :branch,
:project_id, :project_id,
:main_script, :main_script,
@ -73,7 +44,6 @@ class ProductBuildList < ActiveRecord::Base
scope :default_order, -> { order(updated_at: :desc) } scope :default_order, -> { order(updated_at: :desc) }
scope :for_status, -> (status) { where(status: status) if status.present? }
scope :for_user, -> (user) { where(user_id: user.id) } scope :for_user, -> (user) { where(user_id: user.id) }
scope :scoped_to_product_name, -> (product_name) { scope :scoped_to_product_name, -> (product_name) {
joins(:product).where('products.name LIKE ?', "%#{product_name}%") if product_name.present? joins(:product).where('products.name LIKE ?', "%#{product_name}%") if product_name.present?
@ -87,123 +57,14 @@ class ProductBuildList < ActiveRecord::Base
after_initialize :init_project, if: :new_record? after_initialize :init_project, if: :new_record?
after_create :add_job_to_abf_worker_queue
before_destroy :can_destroy?
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
# build_canceling: :build_completed - Worker hasn't time to cancel building because build had been already completed
event :build_success do
transition [:build_started, :build_canceling] => :build_completed
end
# build_canceling: :build_failed - Worker hasn't time to cancel building because build had been already failed
event :build_error do
transition [:build_started, :build_canceling] => :build_failed
end
HUMAN_STATUSES.each do |code,name|
state name, value: code
end
end
def build_started?
status == BUILD_STARTED
end
def build_canceling?
status == BUILD_CANCELING
end
def can_destroy?
![BUILD_STARTED, BUILD_PENDING, BUILD_CANCELING].include?(status)
end
def can_cancel?
[BUILD_STARTED, BUILD_PENDING].include?(status)
end
def event_log_message def event_log_message
{product: product.name}.inspect {product: product.name}.inspect
end end
def self.human_status(status)
I18n.t("layout.product_build_lists.statuses.#{HUMAN_STATUSES[status]}")
end
def human_status
self.class.human_status(status)
end
def can_destroy?
[BUILD_COMPLETED, BUILD_FAILED, BUILD_CANCELED].include? status
end
def sha1_of_file_store_files
(results || []).map{ |r| r['sha1'] }.compact
end
protected protected
def init_project def init_project
self.project ||= product.try(:project) self.project ||= product.try(:project)
end end
def abf_worker_priority
''
end
def abf_worker_base_queue
'iso_worker'
end
def abf_worker_args
file_name = "#{project.name}-#{commit_hash}"
opts = default_url_options
opts.merge!({user: user.authentication_token, password: ''}) if user.present?
srcpath = url_helpers.archive_url(
project.name_with_owner,
file_name,
'tar.gz',
opts
)
cmd_params = "BUILD_ID=#{id} "
if product.platform.hidden?
token = product.platform.tokens.by_active.where(description: CACHED_CHROOT_TOKEN_DESCRIPTION).first
cmd_params << "TOKEN=#{token.authentication_token} " if token
end
cmd_params << params.to_s
{
id: id,
# TODO: remove comment
# srcpath: 'http://dl.dropbox.com/u/945501/avokhmin-test-iso-script-5d9b463d4e9c06ea8e7c89e1b7ff5cb37e99e27f.tar.gz',
srcpath: srcpath,
params: cmd_params,
time_living: time_living,
main_script: main_script,
platform: {
type: product.platform.distrib_type,
name: product.platform.name,
arch: arch.name
},
user: {uname: user.try(:uname), email: user.try(:email)}
}
end
end end

View File

@ -9,14 +9,11 @@ tr
- else - else
td= pbl.id td= pbl.id
td= pbl.human_status td= pbl.human_status
td
- unless pbl.project
= link_to(nil, pbl.container_path)
td td
a href=platform_product_path(platform, product) a href=platform_product_path(platform, product)
= pbl.product.name = pbl.product.name
td.text-center td.text-center
- if can?(:destroy, pbl) && pbl.can_destroy? && !pbl.project - if can?(:destroy, pbl) && pbl.can_destroy?
= link_to platform_product_product_build_list_path(platform, product, pbl), method: :delete, data: { confirm: t('layout.confirm') } do = link_to platform_product_product_build_list_path(platform, product, pbl), method: :delete, data: { confirm: t('layout.confirm') } do
span.glyphicon.glyphicon-remove span.glyphicon.glyphicon-remove

View File

@ -14,7 +14,6 @@
tr tr
th= t("activerecord.attributes.product_build_list.id") th= t("activerecord.attributes.product_build_list.id")
th= t("activerecord.attributes.product_build_list.status") th= t("activerecord.attributes.product_build_list.status")
th= t("activerecord.attributes.product_build_list.container_path")
th= t("activerecord.attributes.product_build_list.product") th= t("activerecord.attributes.product_build_list.product")
th= t("layout.product_build_lists.action") th= t("layout.product_build_lists.action")
th= t("activerecord.attributes.product_build_list.notified_at") th= t("activerecord.attributes.product_build_list.notified_at")

View File

@ -40,7 +40,6 @@
tr tr
th= t("activerecord.attributes.product_build_list.id") th= t("activerecord.attributes.product_build_list.id")
th= t("activerecord.attributes.product_build_list.status") th= t("activerecord.attributes.product_build_list.status")
th= t("activerecord.attributes.product_build_list.container_path")
th= t("activerecord.attributes.product_build_list.product") th= t("activerecord.attributes.product_build_list.product")
th= t("layout.product_build_lists.action") th= t("layout.product_build_lists.action")
th= t("activerecord.attributes.product_build_list.notified_at") th= t("activerecord.attributes.product_build_list.notified_at")

View File

@ -21,6 +21,7 @@ en:
build_started: Build started build_started: Build started
build_canceled: Build canceled build_canceled: Build canceled
build_canceling: Build is canceling build_canceling: Build is canceling
build_completed_partially: Build complete (partially)
ownership: ownership:
header: Build list ownership header: Build list ownership
@ -36,7 +37,6 @@ en:
id: Id id: Id
user: User user: User
product: Product product: Product
container_path: Container
status: Status status: Status
user: User user: User
notified_at: Notified at notified_at: Notified at

View File

@ -21,6 +21,7 @@ ru:
build_started: Собирается build_started: Собирается
build_canceled: Сборка отменена build_canceled: Сборка отменена
build_canceling: Сборка отменяется build_canceling: Сборка отменяется
build_completed_partially: Собран (частично)
ownership: ownership:
header: Принадлежность заданий header: Принадлежность заданий
@ -36,7 +37,6 @@ ru:
id: Id id: Id
user: Пользователь user: Пользователь
product: Продукт product: Продукт
container_path: Контейнер
status: Статус status: Статус
user: Пользователь user: Пользователь
notified_at: Информация получена notified_at: Информация получена

View File

@ -12,28 +12,28 @@ describe ProductBuildList do
it 'is valid given valid attributes' do it 'is valid given valid attributes' do
arch = FactoryGirl.create(:arch, name: 'x86_64') arch = FactoryGirl.create(:arch, name: 'x86_64')
allow(Arch).to receive(:find_by).with(name: 'x86_64').and_return(arch) allow(Arch).to receive(:find_by).with(name: 'x86_64').and_return(arch)
FactoryGirl.create(:product_build_list).should be_truthy expect(FactoryGirl.create(:product_build_list)).to be_truthy
end end
it { should belong_to(:product) } it { is_expected.to belong_to(:product) }
it { should ensure_length_of(:main_script).is_at_most(255) } it { is_expected.to validate_length_of(:main_script).is_at_most(255) }
it { should ensure_length_of(:params).is_at_most(255) } it { is_expected.to validate_length_of(:params).is_at_most(255) }
it { should validate_presence_of(:product_id) } it { is_expected.to validate_presence_of(:product_id) }
it { should validate_presence_of(:status) } it { is_expected.to validate_presence_of(:status) }
ProductBuildList::STATUSES.each do |value| ProductBuildList::STATUSES.each do |value|
it { should allow_value(value).for(:status) } it { is_expected.to allow_value(value).for(:status) }
end end
it { should_not allow_value(555).for(:status) } it { is_expected.to_not allow_value(555).for(:status) }
it { should have_readonly_attribute(:product_id) } it { is_expected.to have_readonly_attribute(:product_id) }
#it { should_not allow_mass_assignment_of(:product_id) } #it { should_not allow_mass_assignment_of(:product_id) }
it { should allow_mass_assignment_of(:status) } it { is_expected.to allow_mass_assignment_of(:status) }
it { should allow_mass_assignment_of(:base_url) } it { is_expected.to allow_mass_assignment_of(:base_url) }
end end
# see app/ability.rb # see app/ability.rb
@ -43,6 +43,6 @@ describe ProductBuildList do
FactoryGirl.create(:product_build_list) FactoryGirl.create(:product_build_list)
user = FactoryGirl.create(:user) user = FactoryGirl.create(:user)
ability = Ability.new user ability = Ability.new user
ProductBuildList.accessible_by(ability).count.should == 1 expect(ProductBuildList.accessible_by(ability).count).to eq 1
end end
end end