Page MenuHomeGRNET

No OneTemporary

File Metadata

Created
Wed, Oct 15, 9:53 AM
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