Page Menu
Home
GRNET
Search
Configure Global Search
Log In
Files
F1090645
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Wed, Oct 15, 9:53 AM
Size
14 KB
Mime Type
text/x-diff
Expires
Fri, Oct 17, 9:53 AM (1 d, 15 h)
Engine
blob
Format
Raw Data
Handle
280563
Attached To
rARCHIVING archiving
View Options
diff --git a/app/controllers/admin/clients_controller.rb b/app/controllers/admin/clients_controller.rb
index 56eaf2e..4ef5a44 100644
--- a/app/controllers/admin/clients_controller.rb
+++ b/app/controllers/admin/clients_controller.rb
@@ -1,73 +1,85 @@
class Admin::ClientsController < Admin::BaseController
- before_action :fetch_client, only: [:show, :jobs, :logs, :stats, :configuration, :disable]
+ before_action :fetch_client, only: [:show, :jobs, :logs, :stats,
+ :configuration, :disable, :revoke]
before_action :fetch_logs, only: [:logs]
# Shows all available clients
#
# GET /admin/clients
def index
@clients = Client.includes(:jobs).all
@client_ids = @clients.map(&:id)
fetch_jobs_info
end
# Shows a specific client
#
# GET /admin/clients/1
def show
if !@client.host.present?
flash[:alert] = 'Client not configured through Archiving'
return redirect_to admin_clients_path
end
get_charts
end
# GET /admin/clients/1/jobs
def jobs
@jobs = @client.recent_jobs.page(params[:page])
end
# GET /admin/clients/1/logs
def logs
end
# GET /admin/clients/1/stats
# POST /admin/clients/1/stats
def stats
get_charts
end
# GET /admin/clients/1/configuration
def configuration
end
# POST /admin/clients/1/disable
def disable
if @client.host.disable_jobs_and_update
flash[:success] = 'Client disabled'
else
flash[:error] = 'Something went wrong, try again later'
end
redirect_to admin_client_path(@client)
end
+ # DELETE /admin/clients/1/revoke
+ def revoke
+ if @client.host.remove_from_bacula(true)
+ flash[:success] = 'Client removed. It will be visible to until its jobs get cleaned up'
+ else
+ flash[:error] = 'Something went wrong, try again later'
+ end
+
+ redirect_to admin_clients_path
+ end
+
private
# Fetches the client based on the given id
def fetch_client
@client = Client.find(params[:id])
@client_ids = [@client.id]
end
def fetch_jobs_info
@stats = JobStats.new
end
def get_charts
days_ago = params.fetch(:days_back, 7).to_i rescue 7
@job_status = ChartGenerator.job_statuses(@client_ids, days_ago)
@job_stats = ChartGenerator.job_stats(@client_ids, days_ago - 1)
end
end
diff --git a/app/models/host.rb b/app/models/host.rb
index 3a252a1..9c510a1 100644
--- a/app/models/host.rb
+++ b/app/models/host.rb
@@ -1,247 +1,249 @@
# The bacula database must be independent from all of our application logic.
# For this reason we have Host which is the application equivalent of a Bacula Client.
#
# A host is being created from our application. When it receives all the configuration
# which is required it gets dispatched to bacula through some configuration files. After
# that, a client with the exact same config is generated by bacula.
class Host < ActiveRecord::Base
include Configuration::Host
STATUSES = {
pending: 0,
configured: 1,
dispatched: 2,
deployed: 3,
updated: 4,
redispatched: 5,
for_removal: 6,
inactive: 7
}
has_many :ownerships
has_many :users, through: :ownerships, inverse_of: :hosts
belongs_to :client, class_name: :Client, foreign_key: :name, primary_key: :name
belongs_to :verifier, class_name: :User, foreign_key: :verifier_id, primary_key: :id
has_many :filesets, dependent: :destroy
has_many :job_templates, dependent: :destroy
has_many :schedules, dependent: :destroy
validates :file_retention, :job_retention,
:port, :password, presence: true
validates :port, numericality: true
validates :fqdn, presence: true, uniqueness: true
validate :fqdn_format
scope :not_baculized, -> {
joins("left join Client on Client.Name = hosts.name").where(Client: { Name: nil })
}
scope :unverified, -> { where(verified: false) }
before_validation :set_retention, :unset_baculized, :sanitize_name
state_machine :status, initial: :pending do
STATUSES.each do |status_name, value|
state status_name, value: value
end
after_transition [:dispatched, :redispatched, :configured, :updated] => :deployed do |host|
host.job_templates.enabled.
update_all(baculized: true, baculized_at: Time.now, updated_at: Time.now)
end
event :add_configuration do
transition [:pending, :dispatched, :inactive] => :configured
end
event :dispatch do
transition :configured => :dispatched
end
event :redispatch do
transition :updated => :redispatched
end
event :set_deployed do
transition [:dispatched, :redispatched, :configured, :updated] => :deployed
end
event :change_deployed_config do
transition [:deployed, :redispatched, :for_removal] => :updated
end
event :mark_for_removal do
transition [:dispatched, :deployed, :updated, :redispatched] => :for_removal
end
event :set_inactive do
transition [:deployed, :dispatched, :updated, :redispatched] => :inactive
end
event :disable do
transition all => :pending
end
end
# Shows the host's auto_prune setting
def auto_prune_human
client_settings[:autoprune]
end
# Uploads the host's config to bacula
# Reloads bacula server
#
# It updates the host's status accordingly
def dispatch_to_bacula
return false if not needs_dispatch?
bacula_handler.deploy_config
end
# Removes a Host from bacula configuration.
# Reloads bacula server
#
# If all go well it changes the host's status and returns true
- def remove_from_bacula
- return false unless needs_revoke?
+ #
+ # @param force[Boolean] forces removal
+ def remove_from_bacula(force=false)
+ return false if not (force || needs_revoke?)
bacula_handler.undeploy_config
end
# Restores a host's backup to a preselected location
#
# @param fileset_id[Integer] the desired fileset
# @param location[String] the desired restore location
# @param restore_point[Datetime] the desired restore_point datetime
def restore(file_set_id, location, restore_point=nil)
return false if not restorable?
job_ids = client.get_job_ids(file_set_id, restore_point)
file_set_name = FileSet.find(file_set_id).file_set
bacula_handler.restore(job_ids, file_set_name, location)
end
# Runs the given backup job ASAP
def backup_now(job_name)
bacula_handler.backup_now(job_name)
end
# Disables all jobs and sends the configuration to Bacula
def disable_jobs_and_update
job_templates.update_all(enabled: false)
bacula_handler.deploy_config
end
# Determinex weather a host:
#
# * has all it takes to be deployed but
# * the config is not yet sent to bacula
#
# @return [Boolean]
def needs_dispatch?
verified? && (can_dispatch? || can_redispatch?)
end
# Determines weather a host is marked for removal
#
# @return [Boolean]
def needs_revoke?
for_removal?
end
# Handles the host's job changes by updating the host's status
def recalculate
add_configuration || change_deployed_config
end
# Fetches an info message concerning the host's deploy status
def display_message
if !verified?
{ message: 'Your host needs to be verified by an admin', severity: :alert }
elsif pending?
{ message: 'client not configured yet', severity: :alert }
elsif configured? || dispatched?
{ message: 'client not deployed to Bacula', severity: :alert }
elsif updated? || redispatched?
{ message: 'client configuration changed, deploy needed', severity: :alert }
elsif for_removal?
{ message: 'pending client configuration withdraw', severity: :error }
elsif inactive?
{ message: 'client disabled', severity: :alert }
end
end
# Determines if a host can issue a restore job.
#
# @returns [Boolean] true if the host's client can issue a restore job
def restorable?
client.present? && client.is_backed_up?
end
# @return [User] the first of the host's users
def first_user
users.order('ownerships.created_at asc').first
end
# Marks the host as verified and sets the relevant metadata
#
# @param admin_verifier[Integer] the verifier's id
def verify(admin_verifier)
self.verified = true
self.verifier_id = admin_verifier
self.verified_at = Time.now
save
end
# Determines if a host can be disabled or not.
# Equivalent to is_deployed
#
# @return [Boolean]
def can_be_disabled?
dispatched? || deployed? || updated? || redispatched?
end
private
# automatic setters
def sanitize_name
self.name = fqdn
end
# Sets the file and job retention according to the global settings
def set_retention
self.file_retention = client_settings[:file_retention]
self.file_retention_period_type = client_settings[:file_retention_period_type]
self.job_retention = client_settings[:job_retention]
self.job_retention_period_type = client_settings[:job_retention_period_type]
end
def unset_baculized
self.baculized = false if new_record?
true
end
# validation
def fqdn_format
regex = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63}$)/
unless fqdn =~ regex
self.errors.add(:fqdn)
end
end
# Proxy object for handling bacula directives
def bacula_handler
BaculaHandler.new(self)
end
# Fetches and memoizes the general configuration settings for Clients
#
# @see ConfigurationSetting.current_client_settings
# @return [Hash] containing the settings
def client_settings
@client_settings ||= ConfigurationSetting.current_client_settings
end
end
diff --git a/app/views/admin/clients/show.html.erb b/app/views/admin/clients/show.html.erb
index 98a7ae8..d94b8b3 100644
--- a/app/views/admin/clients/show.html.erb
+++ b/app/views/admin/clients/show.html.erb
@@ -1,30 +1,33 @@
<%= render partial: 'header' %>
<div class="row right">
<% if !@client.host.inactive? %>
<%= link_to 'Disable client', disable_admin_client_path(@client), method: :post,
data: { confirm: 'This will disable the client. Are you sure?' },
class: "btn btn-warning", role: "button" %>
<% end %>
+ <%= link_to 'Remove client', revoke_admin_client_path(@client), method: :delete,
+ data: { confirm: 'This will REMOVE the client from Bacula. Are you sure?' },
+ class: "btn btn-danger", role: "button" %>
</div>
<div class="row">
<div class="col-xs-4">
<h3>Client Details</h3>
</div>
<div class="col-xs-6">
<h3>Bacula Jobs</h3>
</div>
</div>
<div class="row">
<%= render partial: 'client_details' %>
<div class="col-xs-8">
<div class="row">
<% if @client.host %>
<%= render partial: 'jobs' %>
<% end %>
</div>
</div>
</div>
diff --git a/config/routes.rb b/config/routes.rb
index 671ef9d..fb6e90c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,78 +1,79 @@
Rails.application.routes.draw do
root 'application#index'
post 'login' => 'application#login'
match 'vima', to: 'application#vima', :via => [:get, :post]
get 'logout' => 'application#logout'
resources :clients, only: [:index, :show] do
member do
get :jobs
get :logs
get :stats
post :stats
get :users
get :restore
post :run_restore
end
collection do
post :index
end
end
resources :hosts, only: [:new, :create, :show, :edit, :update, :destroy] do
member do
post :submit_config
post :disable
delete :revoke
end
resources :jobs, only: [:new, :create, :show, :edit, :update, :destroy] do
member do
patch :toggle_enable
post :backup_now
end
end
resources :filesets, only: [:show, :new, :create, :destroy]
resources :schedules, only: [:show, :new, :edit, :create, :update, :destroy]
end
namespace :admin do
match '/', to: 'base#index', via: [:get, :post]
resources :settings, only: [:index, :new, :create, :edit, :update] do
member do
delete :reset
end
end
resources :clients, only: [:index, :show] do
member do
get :jobs
get :logs
get :stats
post :stats
get :configuration
post :disable
+ delete :revoke
end
end
resources :hosts, only: [:show] do
collection do
get :unverified
end
member do
post :verify
end
end
resources :users, only: [:index] do
member do
patch :ban
patch :unban
end
end
end
end
diff --git a/spec/routing/admin/clients_routing_spec.rb b/spec/routing/admin/clients_routing_spec.rb
index ebe022d..aa7377b 100644
--- a/spec/routing/admin/clients_routing_spec.rb
+++ b/spec/routing/admin/clients_routing_spec.rb
@@ -1,42 +1,47 @@
require 'spec_helper'
describe Admin::ClientsController do
it 'routes GET /admin/clients' do
expect(get('/admin/clients')).to route_to(controller: 'admin/clients', action: 'index')
end
it 'routes GET /admin/clients/1' do
expect(get('/admin/clients/1')).
to route_to(controller: 'admin/clients', action: 'show', id: '1')
end
it 'routes GET /admin/clients/1/stats' do
expect(get('/admin/clients/1/stats')).
to route_to(controller: 'admin/clients', action: 'stats', id: '1')
end
it 'routes POST /admin/clients/1/stats' do
expect(post('/admin/clients/1/stats')).
to route_to(controller: 'admin/clients', action: 'stats', id: '1')
end
it 'routes GET /admin/clients/1/logs' do
expect(get('/admin/clients/1/logs')).
to route_to(controller: 'admin/clients', action: 'logs', id: '1')
end
it 'routes GET /admin/clients/1/jobs' do
expect(get('/admin/clients/1/jobs')).
to route_to(controller: 'admin/clients', action: 'jobs', id: '1')
end
it 'routes GET /admin/clients/1/configuration' do
expect(get('/admin/clients/1/configuration')).
to route_to(controller: 'admin/clients', action: 'configuration', id: '1')
end
it 'routes POST /admin/clients/1/disable' do
expect(post('/admin/clients/1/disable')).
to route_to(controller: 'admin/clients', action: 'disable', id: '1')
end
+
+ it 'routes DELETE /admin/clients/1/revoke' do
+ expect(delete('/admin/clients/1/revoke')).
+ to route_to(controller: 'admin/clients', action: 'revoke', id: '1')
+ end
end
Event Timeline
Log In to Comment