Merge branch 'master' into 369-bootstrap

Conflicts:
	config/environments/production.rb
This commit is contained in:
Alexander Machehin 2014-04-21 15:16:30 +06:00
commit 67031c06f8
35 changed files with 486 additions and 169 deletions

View File

@ -0,0 +1,91 @@
ActiveAdmin.register NodeInstruction do
menu priority: 3
controller do
def scoped_collection
NodeInstruction.includes(:user)
end
end
filter :user_uname, as: :string
filter :status, as: :select, collection: NodeInstruction::STATUSES
filter :updated_at
index do
column :id
column :user
column(:status, sortable: :status) do |ni|
status_tag(ni.status, status_color(ni))
end
column :updated_at
default_actions
end
form do |f|
f.inputs do
f.input :user, as: :select, include_blank: false, collection: User.system.map { |u| [u.uname, u.id] }
f.input :status, as: :select, include_blank: false, collection: NodeInstruction::STATUSES
f.input :instruction, as: :text
end
f.actions
end
show do
attributes_table do
row :id
row :user
row(:status, sortable: :status) do |ni|
status_tag(ni.status, status_color(ni))
end
row :created_at
row :updated_at
row :instruction
row(:output) do |ni|
ni.output.to_s.lines.join('<br/>').html_safe
end
end
end
sidebar 'Actions', only: :show do
%w(disable ready restart restart_failed).each do |state|
div do
link_to state.humanize, force_admin_node_instruction_path(resource, state: state), method: :patch
end if resource.send("can_#{state}?")
end
end
sidebar 'Actions', only: :index do
locked = NodeInstruction.all_locked?
span(class: "status_tag #{locked ? 'red' : 'green'}") do
if locked
link_to 'Unlock instructions', unlock_all_admin_node_instructions_path, method: :post
else
link_to 'Lock instructions', lock_all_admin_node_instructions_path, method: :post
end
end
end
collection_action :lock_all, method: :post do
NodeInstruction.lock_all
flash[:notice] = 'Locked successfully'
redirect_to admin_node_instructions_path
end
collection_action :unlock_all, method: :post do
NodeInstruction.unlock_all
flash[:notice] = 'Unlocked successfully'
redirect_to admin_node_instructions_path
end
member_action :force, method: :patch do
resource.send(params[:state])
flash[:notice] = 'Updated successfully'
redirect_to admin_node_instruction_path(resource)
end
end

View File

@ -0,0 +1,12 @@
def status_color(ni)
case
when ni.ready?
:green
when ni.disabled?
nil
when ni.failed?
:red
else
:orange
end
end

View File

@ -9,7 +9,9 @@ class Api::V1::JobsController < Api::V1::BaseController
def shift def shift
@build_list = BuildList.next_build if current_user.system? @build_list = BuildList.next_build if current_user.system?
unless @build_list if @build_list
set_builder
else
platform_ids = Platform.where(name: params[:platforms].split(',')).pluck(:id) if params[:platforms].present? platform_ids = Platform.where(name: params[:platforms].split(',')).pluck(:id) if params[:platforms].present?
arch_ids = Arch.where(name: params[:arches].split(',')).pluck(:id) if params[:arches].present? arch_ids = Arch.where(name: params[:arches].split(',')).pluck(:id) if params[:arches].present?
build_lists = BuildList.for_status(BuildList::BUILD_PENDING).scoped_to_arch(arch_ids). build_lists = BuildList.for_status(BuildList::BUILD_PENDING).scoped_to_arch(arch_ids).
@ -19,36 +21,29 @@ class Api::V1::JobsController < Api::V1::BaseController
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if current_user.system? if current_user.system?
@build_list ||= build_lists.external_nodes(:everything).first @build_list ||= build_lists.external_nodes(:everything).first
@build_list.touch if @build_list
else else
@build_list = build_lists.external_nodes(:owned).for_user(current_user).first @build_list = build_lists.external_nodes(:owned).for_user(current_user).first
@build_list ||= build_lists.external_nodes(:everything). @build_list ||= build_lists.external_nodes(:everything).
accessible_by(current_ability, :everything).readonly(false).first accessible_by(current_ability, :everything).readonly(false).first
if @build_list
@build_list.builder = current_user
@build_list.save
end
end end
set_builder
end end
end end
job = { job = {
worker_queue: @build_list.worker_queue_with_priority(false), worker_queue: @build_list.worker_queue_with_priority(false),
worker_class: @build_list.worker_queue_class, worker_class: @build_list.worker_queue_class,
:worker_args => [@build_list.abf_worker_args] :worker_args => [@build_list.abf_worker_args]
} if @build_list } if @build_list
render json: { job: job }.to_json render json: { job: job }.to_json
end end
def statistics def statistics
if params[:uid].present? if params[:uid].present?
RpmBuildNode.create( RpmBuildNode.create(
:id => params[:uid], id: params[:uid],
:user_id => current_user.id, user_id: current_user.id,
:system => current_user.system?, system: current_user.system?,
worker_count: params[:worker_count], worker_count: params[:worker_count],
busy_workers: params[:busy_workers] busy_workers: params[:busy_workers]
) rescue nil ) rescue nil
@ -86,4 +81,12 @@ class Api::V1::JobsController < Api::V1::BaseController
end end
end end
protected
def set_builder
return unless @build_list
@build_list.builder = current_user
@build_list.save
end
end end

View File

@ -1,5 +1,5 @@
class BuildListsPublishTaskManagerJob class BuildListsPublishTaskManagerJob
@queue = :hook @queue = :middle
def self.perform def self.perform
AbfWorker::BuildListsPublishTaskManager.new.run AbfWorker::BuildListsPublishTaskManager.new.run

View File

@ -1,5 +1,5 @@
class BuildListsQueuesMonitoringJob class BuildListsQueuesMonitoringJob
@queue = :hook @queue = :middle
def self.perform def self.perform
Redis.current.smembers('resque:queues').each do |key| Redis.current.smembers('resque:queues').each do |key|

View File

@ -1,5 +1,5 @@
class CleanApiDefenderStatisticsJob class CleanApiDefenderStatisticsJob
@queue = :clone_build @queue = :low
def self.perform def self.perform
deadline = Date.today - 1.month deadline = Date.today - 1.month

View File

@ -1,5 +1,5 @@
class CleanRpmBuildNodeJob class CleanRpmBuildNodeJob
@queue = :hook @queue = :middle
def self.perform def self.perform
RpmBuildNode.all.each do |n| RpmBuildNode.all.each do |n|

View File

@ -0,0 +1,11 @@
class RestartNodesJob
@queue = :low
def self.perform
return if NodeInstruction.all_locked?
available_nodes = RpmBuildNode.all.map{ |n| n.user_id if n.user.try(:system?) }.compact.uniq
NodeInstruction.where(status: NodeInstruction::READY).
where.not(user_id: available_nodes).find_each(&:restart)
end
end

View File

@ -259,8 +259,8 @@ class BuildList < ActiveRecord::Base
end end
end end
later :publish, queue: :clone_build later :publish, queue: :middle
later :add_job_to_abf_worker_queue, queue: :clone_build later :add_job_to_abf_worker_queue, queue: :middle
HUMAN_CONTAINER_STATUSES = { WAITING_FOR_RESPONSE => :waiting_for_publish, HUMAN_CONTAINER_STATUSES = { WAITING_FOR_RESPONSE => :waiting_for_publish,
BUILD_PUBLISHED => :container_published, BUILD_PUBLISHED => :container_published,
@ -576,7 +576,7 @@ class BuildList < ActiveRecord::Base
def delayed_add_job_to_abf_worker_queue(*args) def delayed_add_job_to_abf_worker_queue(*args)
restart_job if status == BUILD_PENDING restart_job if status == BUILD_PENDING
end end
later :delayed_add_job_to_abf_worker_queue, delay: 60, queue: :clone_build later :delayed_add_job_to_abf_worker_queue, delay: 60, queue: :middle
protected protected

View File

@ -6,7 +6,7 @@ module FileStoreClean
destroy_files_from_file_store if Rails.env.production? destroy_files_from_file_store if Rails.env.production?
super super
end end
later :destroy, queue: :clone_build later :destroy, queue: :middle
def sha1_of_file_store_files def sha1_of_file_store_files
raise NotImplementedError, "You should implement this method" raise NotImplementedError, "You should implement this method"
@ -31,7 +31,7 @@ module FileStoreClean
def later_destroy_files_from_file_store(args) def later_destroy_files_from_file_store(args)
destroy_files_from_file_store(args) destroy_files_from_file_store(args)
end end
later :later_destroy_files_from_file_store, queue: :clone_build later :later_destroy_files_from_file_store, queue: :middle
end end
def self.file_exist_on_file_store?(sha1) def self.file_exist_on_file_store?(sha1)

View File

@ -59,7 +59,7 @@ class MassBuild < ActiveRecord::Base
end end
end end
end end
later :build_all, queue: :clone_build later :build_all, queue: :low
def generate_failed_builds_list def generate_failed_builds_list
generate_list BuildList::BUILD_ERROR generate_list BuildList::BUILD_ERROR
@ -75,17 +75,17 @@ class MassBuild < ActiveRecord::Base
bl.cancel bl.cancel
end end
end end
later :cancel_all, queue: :clone_build later :cancel_all, queue: :low
def publish_success_builds(user) def publish_success_builds(user)
publish user, BuildList::SUCCESS, BuildList::FAILED_PUBLISH publish user, BuildList::SUCCESS, BuildList::FAILED_PUBLISH
end end
later :publish_success_builds, queue: :clone_build later :publish_success_builds, queue: :low
def publish_test_failed_builds(user) def publish_test_failed_builds(user)
publish user, BuildList::TESTS_FAILED publish user, BuildList::TESTS_FAILED
end end
later :publish_test_failed_builds, queue: :clone_build later :publish_test_failed_builds, queue: :low
COUNT_STATUSES.each do |stat| COUNT_STATUSES.each do |stat|
stat_count = "#{stat}_count" stat_count = "#{stat}_count"

View File

@ -0,0 +1,88 @@
class NodeInstruction < ActiveRecord::Base
STATUSES = [
DISABLED = 'disabled',
READY = 'ready',
RESTARTING = 'restarting',
FAILED = 'failed'
]
LOCK_KEY = 'NodeInstruction::lock-key'
belongs_to :user
scope :duplicate, -> id, user_id {
where.not(id: id.to_i).where(user_id: user_id, status: STATUSES - [DISABLED])
}
attr_encrypted :instruction, key: APP_CONFIG['keys']['node_instruction_secret_key']
validates :user, presence: true
validates :instruction, presence: true, length: { maximum: 10000 }
validates :status, presence: true
validate -> {
errors.add(:status, 'Can be only single active instruction for each node') if !disabled? && NodeInstruction.duplicate(id.to_i, user_id).exists?
}
attr_accessible :instruction, :user_id, :output, :status
state_machine :status, initial: :ready do
after_transition(on: :restart) do |instruction, transition|
instruction.perform_restart
end
event :ready do
transition %i(ready restarting disabled failed) => :ready
end
event :disable do
transition ready: :disabled
end
event :restart do
transition ready: :restarting
end
event :restart_failed do
transition restarting: :failed
end
end
def perform_restart
restart_failed if NodeInstruction.all_locked?
success = false
stdout = ''
instruction.lines.each do |command|
next if command.blank?
command.chomp!; command.strip!
stdout << %x[ #{command} 2>&1 ]
success = $?.success?
end
build_lists = BuildList.where(builder_id: user_id, external_nodes: [nil, '']).
for_status(BuildList::BUILD_STARTED)
build_lists.find_each do |bl|
bl.update_column(:status, BuildList::BUILD_PENDING)
bl.restart_job
end
update_column(:output, stdout)
success ? ready : restart_failed
end
later :perform_restart, queue: :low
def self.all_locked?
Redis.current.get(LOCK_KEY).present?
end
def self.lock_all
Redis.current.set(LOCK_KEY, 1)
end
def self.unlock_all
Redis.current.del(LOCK_KEY)
end
end

View File

@ -205,7 +205,7 @@ class Platform < ActiveRecord::Base
def destroy def destroy
with_skip {super} # avoid cascade XML RPC requests with_skip {super} # avoid cascade XML RPC requests
end end
later :destroy, queue: :clone_build later :destroy, queue: :low
def default_host def default_host
EventLog.current_controller.request.host_with_port rescue ::Rosa::Application.config.action_mailer.default_url_options[:host] EventLog.current_controller.request.host_with_port rescue ::Rosa::Application.config.action_mailer.default_url_options[:host]
@ -274,7 +274,7 @@ class Platform < ActiveRecord::Base
def fs_clone(old_name = parent.name, new_name = name) def fs_clone(old_name = parent.name, new_name = name)
FileUtils.cp_r "#{parent.path}/repository", path FileUtils.cp_r "#{parent.path}/repository", path
end end
later :fs_clone, queue: :clone_build later :fs_clone, queue: :low
def freeze_platform_and_update_repos def freeze_platform_and_update_repos
if released_changed? && released == true if released_changed? && released == true

View File

@ -109,7 +109,7 @@ class Project < ActiveRecord::Base
end end
def init_mass_import def init_mass_import
Project.perform_later :clone_build, :run_mass_import, url, srpms_list, visibility, owner, add_to_repository_id Project.perform_later :low, :run_mass_import, url, srpms_list, visibility, owner, add_to_repository_id
end end
def name_with_owner def name_with_owner
@ -417,7 +417,7 @@ class Project < ActiveRecord::Base
PullRequest.where(from_project_id: id).each{ |p| p.update_relations(old_name) } PullRequest.where(from_project_id: id).each{ |p| p.update_relations(old_name) }
pull_requests.where('from_project_id != to_project_id').each(&:update_relations) pull_requests.where('from_project_id != to_project_id').each(&:update_relations)
end end
later :update_path_to_project, queue: :clone_build later :update_path_to_project, queue: :middle
def check_default_branch def check_default_branch
if self.repo.branches.count > 0 && self.repo.branches.map(&:name).exclude?(self.default_branch) if self.repo.branches.count > 0 && self.repo.branches.map(&:name).exclude?(self.default_branch)

View File

@ -57,7 +57,7 @@ class PullRequest < ActiveRecord::Base
system 'git', 'remote', 'set-url', 'head', from_project.path if cross_pull? system 'git', 'remote', 'set-url', 'head', from_project.path if cross_pull?
end end
end end
later :update_relations, queue: :clone_build later :update_relations, queue: :middle
def cross_pull? def cross_pull?
from_project_id != to_project_id from_project_id != to_project_id

View File

@ -59,8 +59,7 @@ class Repository < ActiveRecord::Base
from.projects.find_each {|p| self.projects << p} from.projects.find_each {|p| self.projects << p}
end end
end end
later :clone_relations, loner: true, queue: :clone_build later :clone_relations, loner: true, queue: :low
def add_projects(list, user) def add_projects(list, user)
current_ability = Ability.new(user) current_ability = Ability.new(user)
list.lines.each do |line| list.lines.each do |line|
@ -75,7 +74,7 @@ class Repository < ActiveRecord::Base
end end
end end
end end
later :add_projects, queue: :clone_build later :add_projects, queue: :middle
def remove_projects(list) def remove_projects(list)
list.lines.each do |name| list.lines.each do |name|
@ -87,7 +86,7 @@ class Repository < ActiveRecord::Base
end end
end end
end end
later :remove_projects, queue: :clone_build later :remove_projects, queue: :middle
def full_clone(attrs = {}) def full_clone(attrs = {})
base_clone(attrs).tap do |c| base_clone(attrs).tap do |c|
@ -148,7 +147,7 @@ class Repository < ActiveRecord::Base
def destroy def destroy
with_skip {super} # avoid cascade XML RPC requests with_skip {super} # avoid cascade XML RPC requests
end end
later :destroy, queue: :clone_build later :destroy, queue: :low
def self.custom_sort(repos) def self.custom_sort(repos)
repos.select{ |r| SORT.keys.include?(r.name) }.sort{ |a,b| SORT[a.name] <=> SORT[b.name] } | repos.sort_by(&:name) repos.select{ |r| SORT.keys.include?(r.name) }.sort{ |a,b| SORT[a.name] <=> SORT[b.name] } | repos.sort_by(&:name)

View File

@ -31,7 +31,7 @@ json.build_list do
json.builder do json.builder do
json.fullname @build_list.builder.try(:fullname) json.fullname @build_list.builder.try(:fullname)
json.path user_path(@build_list.builder) json.path user_path(@build_list.builder)
end if @build_list.builder end if @build_list.builder && (!@build_list.builder.system? || current_user.try(:admin?))
json.advisory do json.advisory do
json.(@build_list.advisory, :description, :advisory_id) json.(@build_list.advisory, :description, :advisory_id)

View File

@ -1,5 +1,5 @@
= javascript_include_tag 'codemirror_editor' = javascript_include_tag 'codemirror_editor'
= stylesheet_link_tag 'codemirror' = stylesheet_link_tag 'codemirror_editor'
%h3= t("layout.projects.files_in_project") %h3= t("layout.projects.files_in_project")
.files .files

View File

@ -14,6 +14,7 @@ common: &common
port: 6379 port: 6379
keys: keys:
key_pair_secret_key: 'key_pair_secret_key' key_pair_secret_key: 'key_pair_secret_key'
node_instruction_secret_key: 'node_instruction_secret_key'
airbrake_api_key: 'airbrake_api_key' airbrake_api_key: 'airbrake_api_key'
logentries_key: 'logentries_key' logentries_key: 'logentries_key'
secret_token: 'secret_token' secret_token: 'secret_token'

View File

@ -14,6 +14,7 @@ common: &common
port: 6379 port: 6379
keys: keys:
key_pair_secret_key: 'key_pair_secret_key' key_pair_secret_key: 'key_pair_secret_key'
node_instruction_secret_key: 'node_instruction_secret_key'
airbrake_api_key: 'airbrake_api_key' airbrake_api_key: 'airbrake_api_key'
devise_pepper: 'e295a79fb7966e94a6e8b184ba65791a' devise_pepper: 'e295a79fb7966e94a6e8b184ba65791a'
secret_token: 'e295a79fb7966e94a6e8b184ba65791a' secret_token: 'e295a79fb7966e94a6e8b184ba65791a'

View File

@ -65,7 +65,7 @@ Rosa::Application.configure do
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
config.assets.precompile += config.assets.precompile +=
%w(login.css login.js reg_session.css tour.css tour.js gollum/editor/langs/*.js moment/ru.js %w(login.css login.js reg_session.css tour.css tour.js gollum/editor/langs/*.js moment/ru.js
codemirror_editor.js codemirror.css new_application.css new_application.js) codemirror_editor.js codemirror_editor.css codemirror.css new_application.css new_application.js)
# Compress JavaScripts and CSS. # Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier config.assets.js_compressor = :uglifier

View File

@ -64,7 +64,7 @@ Rosa::Application.configure do
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
config.assets.precompile += config.assets.precompile +=
%w(login.css login.js reg_session.css tour.css tour.js gollum/editor/langs/*.js moment/ru.js %w(login.css login.js reg_session.css tour.css tour.js gollum/editor/langs/*.js moment/ru.js
codemirror_editor.js codemirror.css) codemirror_editor.js codemirror_editor.css)
# Compress JavaScripts and CSS. # Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier config.assets.js_compressor = :uglifier

View File

@ -2,26 +2,33 @@ clean_rpm_build_nodes:
every: every:
- '1m' - '1m'
class: 'CleanRpmBuildNodeJob' class: 'CleanRpmBuildNodeJob'
queue: hook queue: middle
description: 'Cleans RPM build nodes' description: 'Cleans RPM build nodes'
build_lists_publish_task_manager: build_lists_publish_task_manager:
every: every:
- '3m' - '3m'
class: 'BuildListsPublishTaskManagerJob' class: 'BuildListsPublishTaskManagerJob'
queue: hook queue: middle
description: 'Creates tasks for publishing' description: 'Creates tasks for publishing'
build_lists_queues_monitoring: build_lists_queues_monitoring:
every: every:
- '1m' - '1m'
class: 'BuildListsQueuesMonitoringJob' class: 'BuildListsQueuesMonitoringJob'
queue: hook queue: middle
description: 'Monitoring for "user/mass-build" queues' description: 'Monitoring for "user/mass-build" queues'
clean_api_defender_statistics: clean_api_defender_statistics:
every: every:
- '1d' - '1d'
class: 'CleanApiDefenderStatisticsJob' class: 'CleanApiDefenderStatisticsJob'
queue: clone_build queue: low
description: 'Cleans ApiDefender statistics' description: 'Cleans ApiDefender statistics'
restart_nodes:
every:
- '5m'
class: 'RestartNodesJob'
queue: low
description: 'Restarts unavailable nodes'

View File

@ -0,0 +1,12 @@
class CreateNodeInstructions < ActiveRecord::Migration
def change
create_table :node_instructions do |t|
t.integer :user_id, null: false
t.text :encrypted_instruction, null: false
t.text :output
t.string :status
t.timestamps
end
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140407181059) do ActiveRecord::Schema.define(version: 20140414195426) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -287,6 +287,56 @@ ActiveRecord::Schema.define(version: 20140407181059) do
t.boolean "increase_release_tag", default: false, null: false t.boolean "increase_release_tag", default: false, null: false
end end
create_table "users", force: true do |t|
t.string "name"
t.string "email", default: "", null: false
t.string "encrypted_password", limit: 128, default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at"
t.datetime "updated_at"
t.text "ssh_key"
t.string "uname"
t.string "role"
t.string "language", default: "en"
t.integer "own_projects_count", default: 0, null: false
t.text "professional_experience"
t.string "site"
t.string "company"
t.string "location"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "authentication_token"
t.integer "build_priority", default: 50
t.boolean "sound_notifications", default: true
t.index ["authentication_token"], :name => "index_users_on_authentication_token"
t.index ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true
t.index ["email"], :name => "index_users_on_email", :unique => true
t.index ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
t.index ["uname"], :name => "index_users_on_uname", :unique => true
t.index ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true
end
create_table "node_instructions", force: true do |t|
t.integer "user_id", null: false
t.text "encrypted_instruction", null: false
t.text "output"
t.string "status"
t.datetime "created_at"
t.datetime "updated_at"
t.index ["user_id"], :name => "fk__node_instructions_user_id"
t.foreign_key ["user_id"], "users", ["id"], :on_update => :no_action, :on_delete => :no_action, :name => "fk_node_instructions_user_id"
end
create_table "platform_arch_settings", force: true do |t| create_table "platform_arch_settings", force: true do |t|
t.integer "platform_id", null: false t.integer "platform_id", null: false
t.integer "arch_id", null: false t.integer "arch_id", null: false
@ -530,43 +580,4 @@ ActiveRecord::Schema.define(version: 20140407181059) do
t.index ["subject_id", "subject_type"], :name => "index_tokens_on_subject_id_and_subject_type" t.index ["subject_id", "subject_type"], :name => "index_tokens_on_subject_id_and_subject_type"
end end
create_table "users", force: true do |t|
t.string "name"
t.string "email", default: "", null: false
t.string "encrypted_password", limit: 128, default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at"
t.datetime "updated_at"
t.text "ssh_key"
t.string "uname"
t.string "role"
t.string "language", default: "en"
t.integer "own_projects_count", default: 0, null: false
t.text "professional_experience"
t.string "site"
t.string "company"
t.string "location"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "authentication_token"
t.integer "build_priority", default: 50
t.boolean "sound_notifications", default: true
t.index ["authentication_token"], :name => "index_users_on_authentication_token"
t.index ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true
t.index ["email"], :name => "index_users_on_email", :unique => true
t.index ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
t.index ["uname"], :name => "index_users_on_uname", :unique => true
t.index ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true
end
end end

View File

@ -32,9 +32,11 @@ Capistrano::Configuration.instance(:must_exist).load do
:fork_import, :fork_import,
:hook, :hook,
:clone_build, :clone_build,
:middle,
:notification :notification
].join(',') ].join(',')
run "cd #{fetch :current_path} && COUNT=#{workers_count} QUEUE=#{queue} #{rails_env} BACKGROUND=yes bundle exec rake resque:workers" run "cd #{fetch :current_path} && COUNT=#{workers_count - 1} QUEUE=#{queue} INTERVAL=0.1 #{rails_env} BACKGROUND=yes bundle exec rake resque:workers"
run "cd #{fetch :current_path} && COUNT=1 QUEUE=low #{rails_env} BACKGROUND=yes bundle exec rake resque:workers"
end end
def remote_file_exists?(full_path) def remote_file_exists?(full_path)

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe Admin::NodeInstructionsController do
it_should_behave_like 'an admin controller'
end

View File

@ -0,0 +1,6 @@
FactoryGirl.define do
factory :node_instruction do
association :user, factory: :system_user
instruction { FactoryGirl.generate(:string) }
end
end

View File

@ -0,0 +1,6 @@
FactoryGirl.define do
factory :rpm_build_node do
id { FactoryGirl.generate(:string) }
user_id { FactoryGirl.create(:user).id }
end
end

View File

@ -12,4 +12,8 @@ FactoryGirl.define do
factory :admin, parent: :user do factory :admin, parent: :user do
role 'admin' role 'admin'
end end
factory :system_user, parent: :user do
role 'system'
end
end end

View File

@ -0,0 +1,40 @@
require 'spec_helper'
describe RestartNodesJob do
it 'ensures that not raises error' do
lambda do
RestartNodesJob.perform
end.should_not raise_exception
end
it 'ensures that do nothing when all instructions disabled' do
NodeInstruction.lock_all
expect(RpmBuildNode).to_not receive(:all)
RestartNodesJob.perform
end
it 'ensures that creates tasks' do
allow_any_instance_of(NodeInstruction).to receive(:perform_restart)
# ABF active node
ni1 = FactoryGirl.create(:node_instruction)
FactoryGirl.create(:rpm_build_node, user_id: ni1.user_id)
# User node
FactoryGirl.create(:rpm_build_node)
FactoryGirl.create(:node_instruction, status: NodeInstruction::DISABLED)
ni2 = FactoryGirl.create(:node_instruction, status: NodeInstruction::RESTARTING)
FactoryGirl.create(:node_instruction, status: NodeInstruction::FAILED)
ni3 = FactoryGirl.create(:node_instruction)
RestartNodesJob.perform
NodeInstruction.where(status: NodeInstruction::RESTARTING).should have(2).items
NodeInstruction.where(status: NodeInstruction::RESTARTING).should include(ni2, ni3)
NodeInstruction.where(status: NodeInstruction::RESTARTING).should_not include(ni1)
end
end

View File

@ -0,0 +1,9 @@
require 'spec_helper'
describe NodeInstruction do
it 'is valid given valid attributes' do
FactoryGirl.build(:node_instruction).should be_valid
end
end

View File

@ -0,0 +1,7 @@
@import "codemirror";
.cm-s-default span.cm-preamble, {color: #b26818; font-weight: bold;}
.cm-s-default span.cm-macro {color: #b218b2;}
.cm-s-default span.cm-section {color: green; font-weight: bold;}
.cm-s-default span.cm-script {color: red;}
.cm-s-default span.cm-issue {color: yellow;}

View File

@ -47,11 +47,11 @@
margin: 0; margin: 0;
padding: 0.08em 0 0 0; padding: 0.08em 0 0 0;
} }
#head ul.actions { #head ul.actions {
float: right; float: right;
} }
/* @section content */ /* @section content */
//#wiki-content { //#wiki-content {
// height: 1%; // height: 1%;
@ -79,7 +79,7 @@
// .has-rightbar #wiki-body { // .has-rightbar #wiki-body {
// width: 68%; // width: 68%;
// } // }
/* @section rightbar */ /* @section rightbar */
#wiki-rightbar { #wiki-rightbar {
background-color: #f7f7f7; background-color: #f7f7f7;
@ -88,12 +88,12 @@
float: right; float: right;
padding: 7px; padding: 7px;
width: 25%; width: 25%;
border-radius: 0.5em; border-radius: 0.5em;
-moz-border-radius: 0.5em; -moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em; -webkit-border-radius: 0.5em;
} }
#wiki-rightbar p { #wiki-rightbar p {
margin: 13px 0 0; margin: 13px 0 0;
} }
@ -109,13 +109,13 @@
padding: 0 0 0.5em 0; padding: 0 0 0.5em 0;
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
} }
/* Back arrow */ /* Back arrow */
#wiki-rightbar p.parent:before { #wiki-rightbar p.parent:before {
color: #666; color: #666;
content: ""; content: "";
} }
#wiki-rightbar h3 { #wiki-rightbar h3 {
font-size: 1.2em; font-size: 1.2em;
color: #333; color: #333;
@ -123,12 +123,12 @@
padding: 0; padding: 0;
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
} }
#wiki-rightbar ul { #wiki-rightbar ul {
margin: 0.5em 0 1em; margin: 0.5em 0 1em;
padding: 0; padding: 0;
} }
#wiki-rightbar ul li { #wiki-rightbar ul li {
color: #bbb; color: #bbb;
margin: 0 0 0 1em; margin: 0 0 0 1em;
@ -136,19 +136,19 @@
line-height: 1.75em; line-height: 1.75em;
list-style-position: inside; list-style-position: inside;
list-style-type: round; list-style-type: round;
} }
#wiki-rightbar #nav ul li a { #wiki-rightbar #nav ul li a {
font-weight: bold; font-weight: bold;
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
} }
/* @section footer */ /* @section footer */
#wiki-footer { #wiki-footer {
clear: both; clear: both;
margin: 2em 0 5em; margin: 2em 0 5em;
} }
.has-rightbar #wiki-footer { .has-rightbar #wiki-footer {
width: 70%; width: 70%;
} }
@ -160,18 +160,18 @@
padding: 0 0 0.2em; padding: 0 0 0.2em;
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
} }
#wiki-footer #footer-content p { #wiki-footer #footer-content p {
margin: 0.5em 0 0; margin: 0.5em 0 0;
padding: 0; padding: 0;
} }
#wiki-footer #footer-content ul.links { #wiki-footer #footer-content ul.links {
margin: 0.5em 0 0; margin: 0.5em 0 0;
overflow: hidden; overflow: hidden;
padding: 0; padding: 0;
} }
#wiki-footer #footer-content ul.links li { #wiki-footer #footer-content ul.links li {
color: #999; color: #999;
float: left; float: left;
@ -180,21 +180,21 @@
padding: 0; padding: 0;
margin-left: 0.75em; margin-left: 0.75em;
} }
#wiki-footer #footer-content ul.links li a { #wiki-footer #footer-content ul.links li a {
font-weight: bold; font-weight: bold;
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
} }
#wiki-footer #footer-content ul.links li:first-child { #wiki-footer #footer-content ul.links li:first-child {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
} }
.ff #wiki-footer #footer-content ul.links li:first-child { .ff #wiki-footer #footer-content ul.links li:first-child {
margin: 0 -0.75em 0 0; margin: 0 -0.75em 0 0;
} }
/* @section page-footer */ /* @section page-footer */
.page #gollum-footer { .page #gollum-footer {
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
@ -221,23 +221,23 @@
margin: 2em 0; margin: 2em 0;
padding: 0; padding: 0;
} }
#wiki-history table, #wiki-history tbody { #wiki-history table, #wiki-history tbody {
border-collapse: collapse; border-collapse: collapse;
padding: 0; padding: 0;
margin: 0; margin: 0;
width: 100%; width: 100%;
} }
#wiki-history table tr { #wiki-history table tr {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
#wiki-history table tr { #wiki-history table tr {
background-color: #ebf2f6; background-color: #ebf2f6;
} }
#wiki-history table tr td { #wiki-history table tr td {
border: 1px solid #c0dce9; border: 1px solid #c0dce9;
font-size: 1.2em; font-size: 1.2em;
@ -245,13 +245,13 @@
margin: 0; margin: 0;
padding: 0.3em 0.7em; padding: 0.3em 0.7em;
} }
#wiki-history table tr td.checkbox { #wiki-history table tr td.checkbox {
min-width: 2em; min-width: 2em;
padding: 0.3em; padding: 0.3em;
width: 24px; width: 24px;
} }
#wiki-history table tr td.checkbox input { #wiki-history table tr td.checkbox input {
cursor: pointer; cursor: pointer;
display: block; display: block;
@ -259,39 +259,39 @@
padding-top: 0.4em; padding-top: 0.4em;
margin-right: -0.2em; margin-right: -0.2em;
} }
#wiki-history table tr:nth-child(2n), #wiki-history table tr:nth-child(2n),
#wiki-history table tr.alt-row { #wiki-history table tr.alt-row {
background-color: #f3f7fa; background-color: #f3f7fa;
} }
#wiki-history table tr.selected { #wiki-history table tr.selected {
background-color: #ffffea !important; background-color: #ffffea !important;
z-index: 100; z-index: 100;
} }
#wiki-history table tr td.commit-name { #wiki-history table tr td.commit-name {
border-left: 0; border-left: 0;
} }
#wiki-history table tr td.commit-name span.time-elapsed { #wiki-history table tr td.commit-name span.time-elapsed {
color: #999; color: #999;
} }
#wiki-history table tr td.author { #wiki-history table tr td.author {
width: 20%; width: 20%;
} }
#wiki-history table tr td.author a { #wiki-history table tr td.author a {
color: #000; color: #000;
font-weight: bold; font-weight: bold;
} }
#wiki-history table tr td.author a span.username { #wiki-history table tr td.author a span.username {
display: block; display: block;
padding-top: 3px; padding-top: 3px;
} }
#wiki-history table tr td img { #wiki-history table tr td img {
background-color: #fff; background-color: #fff;
border: 1px solid #999; border: 1px solid #999;
@ -303,7 +303,7 @@
width: 18px; width: 18px;
padding: 2px; padding: 2px;
} }
#wiki-history table tr td.commit-name a { #wiki-history table tr td.commit-name a {
font-size: 0.9em; font-size: 0.9em;
font-family: 'Monaco', 'Andale Mono', Consolas, 'Courier New', monospace; font-family: 'Monaco', 'Andale Mono', Consolas, 'Courier New', monospace;
@ -338,26 +338,26 @@
color: #000; color: #000;
font-weight: bold; font-weight: bold;
} }
.results #results { .results #results {
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
margin-bottom: 2em; margin-bottom: 2em;
padding-bottom: 2em; padding-bottom: 2em;
} }
.results #results ul { .results #results ul {
margin: 2em 0 0 0; margin: 2em 0 0 0;
padding: 0; padding: 0;
padding-left: 25px; padding-left: 25px;
} }
.results #results ul li { .results #results ul li {
font-size: 1.2em; font-size: 1.2em;
line-height: 1.6em; line-height: 1.6em;
list-style-position: outside; list-style-position: outside;
padding: 0.2em 0; padding: 0.2em 0;
} }
.results #results ul li span.count { .results #results ul li span.count {
color: #999; color: #999;
} }
@ -367,11 +367,11 @@
line-height: 1.6em; line-height: 1.6em;
margin-top: 2em; margin-top: 2em;
} }
.results #footer ul.actions li { .results #footer ul.actions li {
margin: 0 1em 0 0; margin: 0 1em 0 0;
} }
/* @section compare */ /* @section compare */
.compare h1 { .compare h1 {
@ -383,11 +383,11 @@
color: #000; color: #000;
font-weight: bold; font-weight: bold;
} }
.compare #compare-content { .compare #compare-content {
margin-top: 3em; margin-top: 3em;
} }
.compare .data { .compare .data {
border: 1px solid #ddd; border: 1px solid #ddd;
margin-top: 1em; margin-top: 1em;
@ -398,16 +398,16 @@
.compare .data table { .compare .data table {
width: 100%; width: 100%;
} }
.compare .data pre { .compare .data pre {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.compare .data pre div { .compare .data pre div {
padding: 0 0 0 1em; padding: 0 0 0 1em;
} }
.compare .data tr td { .compare .data tr td {
font-family: "Consolas", "Monaco", "Andale Mono", "Courier New", monospace; font-family: "Consolas", "Monaco", "Andale Mono", "Courier New", monospace;
font-size: 1.2em; font-size: 1.2em;
@ -415,11 +415,11 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.compare .data td.line_numbers { .compare .data td.line_numbers {
background: #f7f7f7; background: #f7f7f7;
border-right: 1px solid #999; border-right: 1px solid #999;
color: #999; color: #999;
padding: 0 0 0 0.5em; padding: 0 0 0 0.5em;
width: 1%; width: 1%;
} }
@ -433,7 +433,7 @@
margin-left: 0; margin-left: 0;
margin-right: 0.6em; margin-right: 0.6em;
} }
/* git_access */ /* git_access */
//#wiki-content .url-box { //#wiki-content .url-box {
@ -450,7 +450,7 @@
// padding: 10px 10px 0; // padding: 10px 10px 0;
// width: 100%; // width: 100%;
//} //}
// //
//#wiki-content .url-box ul.clone_urls { //#wiki-content .url-box ul.clone_urls {
// float: left; // float: left;
// height: 23px; // height: 23px;
@ -539,30 +539,30 @@ ul.clone-urls li {
// padding: 3px 5px 2px; // padding: 3px 5px 2px;
// width: 400px; // width: 400px;
//} //}
/* @control syntax */ /* @control syntax */
.highlight { background: #ffffff; } .highlight { background: #ffffff; }
.highlight .c { color: #999988; font-style: italic } .highlight .c { color: #a50; font-style: italic; font-weight: bold; }
.highlight .err { color: #a61717; background-color: #e3d2d2 } .highlight .err { color: #a61717; background-color: #e3d2d2 }
.highlight .k { font-weight: bold } .highlight .k { font-weight: bold }
.highlight .o { font-weight: bold } .highlight .o { font-weight: bold }
.highlight .cm { color: #999988; font-style: italic } .highlight .cm { color: #999988; font-style: italic }
.highlight .cp { color: #999999; font-weight: bold } .highlight .cp { color: #999999; font-weight: bold }
.highlight .c1 { color: #999988; font-style: italic } .highlight .c1 { color: #999988; font-style: italic }
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } .highlight .cs { color: #999999; font-weight: bold; font-style: italic }
.highlight .gd { color: #000000; background-color: #ffdddd } .highlight .gd { color: #000000; background-color: #ffdddd }
.highlight .gd .x { color: #000000; background-color: #ffaaaa } .highlight .gd .x { color: #000000; background-color: #ffaaaa }
.highlight .ge { font-style: italic } .highlight .ge { font-style: italic }
.highlight .gr { color: #aa0000 } .highlight .gr { color: #aa0000 }
.highlight .gh { color: #999999 } .highlight .gh { color: #b26818; font-weight: bold; }
.highlight .gi { color: #000000; background-color: #ddffdd } .highlight .gi { color: #000000; background-color: #ddffdd }
.highlight .gi .x { color: #000000; background-color: #aaffaa } .highlight .gi .x { color: #000000; background-color: #aaffaa }
.highlight .gc { color: #999; background-color: #EAF2F5 } .highlight .gc { color: #999; background-color: #EAF2F5 }
.highlight .go { color: #888888 } .highlight .go { color: #888888 }
.highlight .gp { color: #555555 } .highlight .gp { color: #555555 }
.highlight .gs { font-weight: bold } .highlight .gs { font-weight: bold }
.highlight .gu { color: #aaaaaa } .highlight .gu { color: #aaaaaa }
.highlight .gt { color: #aa0000 } .highlight .gt { color: #aa0000 }
/* @control minibutton */ /* @control minibutton */
@ -656,7 +656,7 @@ ul.actions {
margin: -10% 0 0 -35%; margin: -10% 0 0 -35%;
position: absolute; position: absolute;
width: 70%; width: 70%;
border-radius: 0.5em; border-radius: 0.5em;
-moz-border-radius: 0.5em; -moz-border-radius: 0.5em;
-webkit-border-radius: 0.5em; -webkit-border-radius: 0.5em;
@ -689,7 +689,7 @@ ul.actions {
background: #fff; background: #fff;
border: 1px solid #d4d4d4; border: 1px solid #d4d4d4;
overflow: hidden; overflow: hidden;
border-radius: 0.3em; border-radius: 0.3em;
-moz-border-radius: 0.3em; -moz-border-radius: 0.3em;
-webkit-border-radius: 0.3em; -webkit-border-radius: 0.3em;
@ -702,22 +702,22 @@ ul.actions {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 1.2em; font-size: 1.2em;
height: 1.8em; height: 1.8em;
-webkit-focus-ring: none; -webkit-focus-ring: none;
} }
.ff #head #gollum-searchbar #gollum-searchbar-fauxtext input#search-query { .ff #head #gollum-searchbar #gollum-searchbar-fauxtext input#search-query {
padding: 0.2em 0 0.2em 0.5em; padding: 0.2em 0 0.2em 0.5em;
} }
.ie #head #gollum-searchbar #gollum-searchbar-fauxtext input#search-query { .ie #head #gollum-searchbar #gollum-searchbar-fauxtext input#search-query {
padding: 0.4em 0 0 0.5em; padding: 0.4em 0 0 0.5em;
} }
#head #gollum-searchbar #gollum-searchbar-fauxtext input#search-query.ph { #head #gollum-searchbar #gollum-searchbar-fauxtext input#search-query.ph {
color: #999; color: #999;
} }
#head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit { #head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit {
border: 0; border: 0;
border-left: 1px solid #d4d4d4; border-left: 1px solid #d4d4d4;
@ -726,12 +726,12 @@ ul.actions {
padding: 0; padding: 0;
float: right; float: right;
font-size: 1.2em; font-size: 1.2em;
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0; -moz-border-radius: 0 3px 3px 0;
-webkit-border-radius: 0 3px 3px 0; -webkit-border-radius: 0 3px 3px 0;
} }
#head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit span { #head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit span {
background-image: image-url('gollum/icon-sprite.png'); background-image: image-url('gollum/icon-sprite.png');
background-position: -431px -1px; background-position: -431px -1px;
@ -742,12 +742,12 @@ ul.actions {
text-indent: -5000px; text-indent: -5000px;
width: 28px; width: 28px;
} }
.ff #head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit span, .ff #head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit span,
.ie #head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit span { .ie #head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit span {
height: 2.2em; height: 2.2em;
} }
#head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit:hover span { #head #gollum-searchbar #gollum-searchbar-fauxtext #search-submit:hover span {
background-position: -431px -28px; background-position: -431px -28px;
padding: 0; padding: 0;

View File

@ -48,7 +48,7 @@
.gs { font-weight: bold } /* Generic.Strong */ .gs { font-weight: bold } /* Generic.Strong */
.gu { color: #aaaaaa } /* Generic.Subheading */ .gu { color: #aaaaaa } /* Generic.Subheading */
.gt { color: #aa0000 } /* Generic.Traceback */ .gt { color: #aa0000 } /* Generic.Traceback */
.kc { color: #000000; font-weight: bold } /* Keyword.Constant */ .kc { color: #b218b2; font-weight: bold } /* Keyword.Constant */
.kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */
.kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */
.kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */
@ -60,7 +60,7 @@
.nb { color: #0086B3 } /* Name.Builtin */ .nb { color: #0086B3 } /* Name.Builtin */
.nc { color: #445588; font-weight: bold } /* Name.Class */ .nc { color: #445588; font-weight: bold } /* Name.Class */
.no { color: #008080 } /* Name.Constant */ .no { color: #008080 } /* Name.Constant */
.nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ .nd { color: green; font-weight: bold; } /* Name.Decorator */
.ni { color: #800080 } /* Name.Entity */ .ni { color: #800080 } /* Name.Entity */
.ne { color: #990000; font-weight: bold } /* Name.Exception */ .ne { color: #990000; font-weight: bold } /* Name.Exception */
.nf { color: #990000; font-weight: bold } /* Name.Function */ .nf { color: #990000; font-weight: bold } /* Name.Function */
@ -90,3 +90,5 @@
.vg { color: #008080 } /* Name.Variable.Global */ .vg { color: #008080 } /* Name.Variable.Global */
.vi { color: #008080 } /* Name.Variable.Instance */ .vi { color: #008080 } /* Name.Variable.Instance */
.il { color: #009999 } /* Literal.Number.Integer.Long */ .il { color: #009999 } /* Literal.Number.Integer.Long */
.c { color: #a50; } /* Comment */