diff --git a/app/controllers/platforms/product_build_lists_controller.rb b/app/controllers/platforms/product_build_lists_controller.rb index d0e505b15..1198f674e 100644 --- a/app/controllers/platforms/product_build_lists_controller.rb +++ b/app/controllers/platforms/product_build_lists_controller.rb @@ -82,7 +82,9 @@ class Platforms::ProductBuildListsController < Platforms::BaseController scoped_to_product_name(@product_build_list.product_name). for_status(@product_build_list.status) 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 end diff --git a/app/jobs/abf_worker/iso_worker_observer.rb b/app/jobs/abf_worker/iso_worker_observer.rb index b8e5fc142..d6e6fd086 100644 --- a/app/jobs/abf_worker/iso_worker_observer.rb +++ b/app/jobs/abf_worker/iso_worker_observer.rb @@ -11,7 +11,14 @@ module AbfWorker when COMPLETED subject.build_success 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 subject.start_build when CANCELED diff --git a/app/models/concerns/product_build_list/abf_workerable.rb b/app/models/concerns/product_build_list/abf_workerable.rb new file mode 100644 index 000000000..d84047e4b --- /dev/null +++ b/app/models/concerns/product_build_list/abf_workerable.rb @@ -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 diff --git a/app/models/concerns/product_build_list/statusable.rb b/app/models/concerns/product_build_list/statusable.rb new file mode 100644 index 000000000..b1ab7ee49 --- /dev/null +++ b/app/models/concerns/product_build_list/statusable.rb @@ -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 diff --git a/app/models/product_build_list.rb b/app/models/product_build_list.rb index df66186a0..e98799658 100644 --- a/app/models/product_build_list.rb +++ b/app/models/product_build_list.rb @@ -3,39 +3,13 @@ class ProductBuildList < ActiveRecord::Base include TimeLiving include FileStoreClean include UrlHelper - include AbfWorkerMethods include EventLoggable - - delegate :url_helpers, to: 'Rails.application.routes' + include ProductBuildList::Statusable + include ProductBuildList::AbfWorkerable LIVE_TIME = 2.week # for autostart 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 :project belongs_to :arch @@ -47,17 +21,14 @@ class ProductBuildList < ActiveRecord::Base before_validation -> { self.not_delete = false unless build_completed?; true } validates :product, :product_id, - :status, :project, :project_id, :main_script, :arch, :arch_id, presence: true - validates :status, inclusion: { in: STATUSES } validates :main_script, :params, length: { maximum: 255 } attr_accessor :base_url, :product_name - attr_accessible :status, - :base_url, + attr_accessible :base_url, :branch, :project_id, :main_script, @@ -73,7 +44,6 @@ class ProductBuildList < ActiveRecord::Base 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 :scoped_to_product_name, -> (product_name) { 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_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 {product: product.name}.inspect 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 def init_project self.project ||= product.try(:project) 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 diff --git a/app/views/platforms/product_build_lists/_product_build_list.html.slim b/app/views/platforms/product_build_lists/_product_build_list.html.slim index cb1328d0b..2ff11f57e 100644 --- a/app/views/platforms/product_build_lists/_product_build_list.html.slim +++ b/app/views/platforms/product_build_lists/_product_build_list.html.slim @@ -9,14 +9,11 @@ tr - else td= pbl.id td= pbl.human_status - td - - unless pbl.project - = link_to(nil, pbl.container_path) td a href=platform_product_path(platform, product) = pbl.product.name 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 span.glyphicon.glyphicon-remove diff --git a/app/views/platforms/product_build_lists/index.html.slim b/app/views/platforms/product_build_lists/index.html.slim index e63da7921..57743feec 100644 --- a/app/views/platforms/product_build_lists/index.html.slim +++ b/app/views/platforms/product_build_lists/index.html.slim @@ -14,7 +14,6 @@ tr th= t("activerecord.attributes.product_build_list.id") 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("layout.product_build_lists.action") th= t("activerecord.attributes.product_build_list.notified_at") diff --git a/app/views/platforms/products/show.html.slim b/app/views/platforms/products/show.html.slim index f9658c99e..415006abd 100644 --- a/app/views/platforms/products/show.html.slim +++ b/app/views/platforms/products/show.html.slim @@ -40,7 +40,6 @@ tr th= t("activerecord.attributes.product_build_list.id") 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("layout.product_build_lists.action") th= t("activerecord.attributes.product_build_list.notified_at") diff --git a/config/locales/models/product_build_list.en.yml b/config/locales/models/product_build_list.en.yml index 0365be209..d5e3bfd79 100644 --- a/config/locales/models/product_build_list.en.yml +++ b/config/locales/models/product_build_list.en.yml @@ -21,6 +21,7 @@ en: build_started: Build started build_canceled: Build canceled build_canceling: Build is canceling + build_completed_partially: Build complete (partially) ownership: header: Build list ownership @@ -36,7 +37,6 @@ en: id: Id user: User product: Product - container_path: Container status: Status user: User notified_at: Notified at diff --git a/config/locales/models/product_build_list.ru.yml b/config/locales/models/product_build_list.ru.yml index de9f07b3e..ee0f7a07c 100644 --- a/config/locales/models/product_build_list.ru.yml +++ b/config/locales/models/product_build_list.ru.yml @@ -21,6 +21,7 @@ ru: build_started: Собирается build_canceled: Сборка отменена build_canceling: Сборка отменяется + build_completed_partially: Собран (частично) ownership: header: Принадлежность заданий @@ -36,7 +37,6 @@ ru: id: Id user: Пользователь product: Продукт - container_path: Контейнер status: Статус user: Пользователь notified_at: Информация получена diff --git a/spec/models/product_build_list_spec.rb b/spec/models/product_build_list_spec.rb index c9d88a004..c1fd50fd5 100644 --- a/spec/models/product_build_list_spec.rb +++ b/spec/models/product_build_list_spec.rb @@ -12,28 +12,28 @@ describe ProductBuildList do it 'is valid given valid attributes' do arch = FactoryGirl.create(:arch, name: 'x86_64') 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 - it { should belong_to(:product) } + it { is_expected.to belong_to(:product) } - it { should ensure_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(:main_script).is_at_most(255) } + it { is_expected.to validate_length_of(:params).is_at_most(255) } - it { should validate_presence_of(:product_id) } - it { should validate_presence_of(:status) } + it { is_expected.to validate_presence_of(:product_id) } + it { is_expected.to validate_presence_of(:status) } ProductBuildList::STATUSES.each do |value| - it { should allow_value(value).for(:status) } + it { is_expected.to allow_value(value).for(:status) } 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 allow_mass_assignment_of(:status) } - it { should allow_mass_assignment_of(:base_url) } + it { is_expected.to allow_mass_assignment_of(:status) } + it { is_expected.to allow_mass_assignment_of(:base_url) } end # see app/ability.rb @@ -43,6 +43,6 @@ describe ProductBuildList do FactoryGirl.create(:product_build_list) user = FactoryGirl.create(:user) ability = Ability.new user - ProductBuildList.accessible_by(ability).count.should == 1 + expect(ProductBuildList.accessible_by(ability).count).to eq 1 end end