berta 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +26 -0
- data/.travis.yml +19 -0
- data/Gemfile +3 -0
- data/LICENSE +14 -0
- data/README.md +48 -0
- data/Rakefile +17 -0
- data/berta.gemspec +32 -0
- data/bin/berta +4 -0
- data/config/berta.yml +19 -0
- data/config/email.erb +18 -0
- data/lib/berta.rb +13 -0
- data/lib/berta/cli.rb +108 -0
- data/lib/berta/command_executor.rb +18 -0
- data/lib/berta/entities.rb +6 -0
- data/lib/berta/entities/expiration.rb +82 -0
- data/lib/berta/errors.rb +9 -0
- data/lib/berta/errors/backend_error.rb +5 -0
- data/lib/berta/errors/entities.rb +9 -0
- data/lib/berta/errors/entities/invalid_entity_xml_error.rb +7 -0
- data/lib/berta/errors/entities/no_user_email_error.rb +7 -0
- data/lib/berta/errors/opennebula.rb +13 -0
- data/lib/berta/errors/opennebula/authentication_error.rb +7 -0
- data/lib/berta/errors/opennebula/resource_not_found_error.rb +7 -0
- data/lib/berta/errors/opennebula/resource_retrieval_error.rb +7 -0
- data/lib/berta/errors/opennebula/resource_state_error.rb +7 -0
- data/lib/berta/errors/opennebula/stub_error.rb +7 -0
- data/lib/berta/errors/opennebula/user_not_authorized_error.rb +7 -0
- data/lib/berta/errors/standard_error.rb +5 -0
- data/lib/berta/expiration_manager.rb +42 -0
- data/lib/berta/notification_manager.rb +100 -0
- data/lib/berta/service.rb +118 -0
- data/lib/berta/settings.rb +37 -0
- data/lib/berta/utils.rb +6 -0
- data/lib/berta/utils/opennebula.rb +8 -0
- data/lib/berta/utils/opennebula/helper.rb +47 -0
- data/lib/berta/virtual_machine_handler.rb +120 -0
- metadata +304 -0
data/lib/berta/errors.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Berta
|
2
|
+
# Module for Berta error classes
|
3
|
+
module Errors
|
4
|
+
autoload :StandardError, 'berta/errors/standard_error'
|
5
|
+
autoload :BackendError, 'berta/errors/backend_error'
|
6
|
+
autoload :OpenNebula, 'berta/errors/opennebula'
|
7
|
+
autoload :Entities, 'berta/errors/entities'
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Berta
|
2
|
+
module Errors
|
3
|
+
# Module for OpenNebula error classes
|
4
|
+
module OpenNebula
|
5
|
+
autoload :StubError, 'berta/errors/opennebula/stub_error'
|
6
|
+
autoload :AuthenticationError, 'berta/errors/opennebula/authentication_error'
|
7
|
+
autoload :UserNotAuthorizedError, 'berta/errors/opennebula/user_not_authorized_error'
|
8
|
+
autoload :ResourceNotFoundError, 'berta/errors/opennebula/resource_not_found_error'
|
9
|
+
autoload :ResourceStateError, 'berta/errors/opennebula/resource_state_error'
|
10
|
+
autoload :ResourceRetrievalError, 'berta/errors/opennebula/resource_retrieval_error'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Berta
|
2
|
+
# Class for managing expiration dates on vms
|
3
|
+
class ExpirationManager
|
4
|
+
# Update all expirations on vm, removes invalid expirations
|
5
|
+
# and if needed will set default expiration date.
|
6
|
+
#
|
7
|
+
# @param vms [Array<Berta::VirtualMachineHandler>] Virtual machines
|
8
|
+
# to update expiration on.
|
9
|
+
def update_expirations(vms)
|
10
|
+
vms.each do |vm|
|
11
|
+
remove_invalid_expirations(vm)
|
12
|
+
add_default_expiration(vm)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Removes invalid expirations on vm. That are schelude actions
|
17
|
+
# with expiration time later than expiration offset.
|
18
|
+
#
|
19
|
+
# @param vm [Berta::VirtualMachineHandler] Virtual machine to
|
20
|
+
# remove invalid expirations on.
|
21
|
+
def remove_invalid_expirations(vm)
|
22
|
+
exps = vm.expirations
|
23
|
+
exps.keep_if(&:in_expiration_interval?)
|
24
|
+
vm.update_expirations(exps) if exps.length != vm.expirations.length
|
25
|
+
rescue Berta::Errors::BackendError => e
|
26
|
+
logger.error "#{e.message}\n\tOn vm with id #{vm.handle['ID']}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Adds default expiration if no valid expiration with
|
30
|
+
# right expiration action is set.
|
31
|
+
#
|
32
|
+
# @param vm [Berta::VirtualMachineHandler] Virtual machine
|
33
|
+
# to set default expiration on.
|
34
|
+
def add_default_expiration(vm)
|
35
|
+
return if vm.default_expiration
|
36
|
+
vm.add_expiration(Time.now.to_i + Berta::Settings.expiration_offset,
|
37
|
+
Berta::Settings.expiration.action)
|
38
|
+
rescue Berta::Errors::BackendError => e
|
39
|
+
logger.error "#{e.message}\n\tOn vm with id #{vm.handle['ID']}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require 'erb'
|
3
|
+
require 'tilt'
|
4
|
+
|
5
|
+
module Berta
|
6
|
+
# Class for managing notifications, setting and sending them
|
7
|
+
class NotificationManager
|
8
|
+
attr_reader :service, :email_template
|
9
|
+
|
10
|
+
# Creates NotificationManager object with given service.
|
11
|
+
# Notification manager needs service for fetching user
|
12
|
+
# data from opennebula database. Also initializes
|
13
|
+
# email template object.
|
14
|
+
#
|
15
|
+
# @param service [Berta::Service] Service that will be used
|
16
|
+
# for fetching data.
|
17
|
+
def initialize(service)
|
18
|
+
@service = service
|
19
|
+
email_file = 'email.erb'.freeze
|
20
|
+
email_template_path = "#{File.dirname(__FILE__)}/../../config/#{email_file}"
|
21
|
+
email_template_path = "etc/berta/#{email_file}" \
|
22
|
+
if File.exist?("etc/berta/#{email_file}")
|
23
|
+
email_template_path = "#{ENV['HOME']}/.berta/#{email_file}" \
|
24
|
+
if File.exist?("#{ENV['HOME']}/.berta/#{email_file}")
|
25
|
+
@email_template = Tilt.new(email_template_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Notifies users. Finds all users that should be notified
|
29
|
+
# and sends email to each of them. Email is generated
|
30
|
+
# from template.
|
31
|
+
#
|
32
|
+
# @param vms [Array<Berta::VirtualMachineHandler>] Virtual machines
|
33
|
+
# to check for notifications.
|
34
|
+
def notify_users(vms)
|
35
|
+
begin
|
36
|
+
users = service.users
|
37
|
+
rescue Berta::Errors::BackendError => e
|
38
|
+
logger.error e.message
|
39
|
+
return
|
40
|
+
end
|
41
|
+
uids_to_notify(vms).each do |uid, uvms|
|
42
|
+
user = users.find { |usr| usr['ID'] == uid }
|
43
|
+
notify_user(user, uvms) if user
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Notifies given user about given vms.
|
48
|
+
#
|
49
|
+
# @param user [OpenNebula::User] User to notify
|
50
|
+
# @param user_vms [Array<Berta::VirtualMachineHandler>] VMs to notify about
|
51
|
+
def notify_user(user, user_vms)
|
52
|
+
send_notification(user, user_vms)
|
53
|
+
rescue ArgumentError, Berta::Errors::Entities::NoUserEmailError => e
|
54
|
+
logger.error e.message
|
55
|
+
else
|
56
|
+
user_vms.each(&:update_notified)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Finds and return uids of users from vms that should be notified.
|
60
|
+
#
|
61
|
+
# @param vms [Array<VirtualMachineHandler>] VMs to check for notification
|
62
|
+
# @return [Hash<String, Array<VirtualMachineHandler>>] Hash of user ids to
|
63
|
+
# vms that user with key id should be notified about
|
64
|
+
def uids_to_notify(vms)
|
65
|
+
notif = vms.keep_if(&:should_notify?)
|
66
|
+
uidsvm = Hash.new([])
|
67
|
+
notif.each { |vm| uidsvm[vm.handle['UID']] += [vm] }
|
68
|
+
uidsvm
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sends email to given user about given vms using sendmail. Email
|
72
|
+
# is generated from email template.
|
73
|
+
#
|
74
|
+
# @param user [OpenNebula::User] User to notify
|
75
|
+
# @param vms [Array<Berta::VirtualMachineHandler>] VMs to notify user about
|
76
|
+
# @raise [Berta::Errors::Entities::NoUserEmailError] If user has no email set
|
77
|
+
def send_notification(user, vms)
|
78
|
+
user_email = user['TEMPLATE/EMAIL']
|
79
|
+
user_name = user['NAME']
|
80
|
+
raise Berta::Errors::Entities::NoUserEmailError, "User: #{user_name} with id: #{user['ID']} has no email set" \
|
81
|
+
unless user_email
|
82
|
+
mail = Mail.new(email_template.render(Hash,
|
83
|
+
user_email: user_email,
|
84
|
+
user_name: user_name,
|
85
|
+
vms: vms_data(vms)))
|
86
|
+
mail.delivery_method :sendmail
|
87
|
+
mail.deliver
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def vms_data(vms)
|
93
|
+
vms.map do |vm|
|
94
|
+
{ id: vm.handle['ID'],
|
95
|
+
name: vm.handle['NAME'],
|
96
|
+
expiration: vm.default_expiration.time.to_i }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'opennebula'
|
2
|
+
|
3
|
+
module Berta
|
4
|
+
# Berta service for communication with OpenNebula
|
5
|
+
class Service
|
6
|
+
# VM states that take resources
|
7
|
+
RESOURCE_STATES = %w(SUSPENDED POWEROFF CLONING).freeze
|
8
|
+
# Active state has some lcm states that should not expire
|
9
|
+
ACTIVE_STATE = 'ACTIVE'.freeze
|
10
|
+
# LCM states in which active state shouldn't expire
|
11
|
+
NON_RESOURCE_ACTIVE_LCM_STATES = %w(EPILOG SHUTDOWN STOP UNDEPLOY FAILURE).freeze
|
12
|
+
|
13
|
+
attr_reader :endpoint
|
14
|
+
attr_reader :client
|
15
|
+
|
16
|
+
# Initializes service object and connects to opennebula
|
17
|
+
# backend. If both arguments are nil default ONE_AUTH
|
18
|
+
# will be used.
|
19
|
+
#
|
20
|
+
# @param secret [String] Opennebula secret
|
21
|
+
# @param endpoint [String] Endpoint of OpenNebula
|
22
|
+
def initialize(secret, endpoint)
|
23
|
+
@endpoint = endpoint
|
24
|
+
@client = OpenNebula::Client.new(secret, endpoint)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Fetch running vms from OpenNebula and filter out vms that
|
28
|
+
# take no resources.
|
29
|
+
#
|
30
|
+
# @return [Berta::VirtualMachineHandler] Virtual machines
|
31
|
+
# running on OpenNebula
|
32
|
+
# @raise [Berta::Errors::OpenNebula::AuthenticationError]
|
33
|
+
# @raise [Berta::Errors::OpenNebula::UserNotAuthorizedError]
|
34
|
+
# @raise [Berta::Errors::OpenNebula::ResourceNotFoundError]
|
35
|
+
# @raise [Berta::Errors::OpenNebula::ResourceStateError]
|
36
|
+
# @raise [Berta::Errors::OpenNebula::ResourceRetrievalError]
|
37
|
+
def running_vms
|
38
|
+
logger.debug 'Fetching vms'
|
39
|
+
vm_pool = OpenNebula::VirtualMachinePool.new(client)
|
40
|
+
Berta::Utils::OpenNebula::Helper.handle_error { vm_pool.info_all }
|
41
|
+
vm_pool.map { |vm| Berta::VirtualMachineHandler.new(vm) }
|
42
|
+
.delete_if { |vmh| excluded?(vmh) || !takes_resources?(vmh) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fetch users from OpenNebula
|
46
|
+
#
|
47
|
+
# @return [OpenNebula::UserPool] Users on OpenNebula
|
48
|
+
# @raise [Berta::Errors::OpenNebula::AuthenticationError]
|
49
|
+
# @raise [Berta::Errors::OpenNebula::UserNotAuthorizedError]
|
50
|
+
# @raise [Berta::Errors::OpenNebula::ResourceNotFoundError]
|
51
|
+
# @raise [Berta::Errors::OpenNebula::ResourceStateError]
|
52
|
+
# @raise [Berta::Errors::OpenNebula::ResourceRetrievalError]
|
53
|
+
def users
|
54
|
+
logger.debug 'Fetching users'
|
55
|
+
user_pool = OpenNebula::UserPool.new(client)
|
56
|
+
Berta::Utils::OpenNebula::Helper.handle_error { user_pool.info }
|
57
|
+
user_pool
|
58
|
+
end
|
59
|
+
|
60
|
+
# Fetch clusters from OpenNebula
|
61
|
+
#
|
62
|
+
# @return [OpenNebula::ClusterPool] Clusters on OpenNebula
|
63
|
+
# @raise [Berta::Errors::OpenNebula::AuthenticationError]
|
64
|
+
# @raise [Berta::Errors::OpenNebula::UserNotAuthorizedError]
|
65
|
+
# @raise [Berta::Errors::OpenNebula::ResourceNotFoundError]
|
66
|
+
# @raise [Berta::Errors::OpenNebula::ResourceStateError]
|
67
|
+
# @raise [Berta::Errors::OpenNebula::ResourceRetrievalError]
|
68
|
+
def clusters
|
69
|
+
logger.debug 'Fetching clusters'
|
70
|
+
cluster_pool = OpenNebula::ClusterPool.new(client)
|
71
|
+
Berta::Utils::OpenNebula::Helper.handle_error { cluster_pool.info }
|
72
|
+
cluster_pool
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def excluded?(vmh)
|
78
|
+
excluded_id?(vmh) ||
|
79
|
+
excluded_user?(vmh) ||
|
80
|
+
excluded_group?(vmh) ||
|
81
|
+
excluded_cluster?(vmh)
|
82
|
+
end
|
83
|
+
|
84
|
+
def excluded_id?(vmh)
|
85
|
+
Berta::Settings.exclude.ids.find { |id| vmh.handle.id == id } \
|
86
|
+
if vmh.handle.id && Berta::Settings.exclude.ids
|
87
|
+
end
|
88
|
+
|
89
|
+
def excluded_user?(vmh)
|
90
|
+
Berta::Settings.exclude.users.find { |user| vmh.handle['UNAME'] == user } \
|
91
|
+
if vmh.handle['UNAME'] && Berta::Settings.exclude.users
|
92
|
+
end
|
93
|
+
|
94
|
+
def excluded_group?(vmh)
|
95
|
+
Berta::Settings.exclude.groups.find { |group| vmh.handle['GNAME'] == group } \
|
96
|
+
if vmh.handle['GNAME'] && Berta::Settings.exclude.groups
|
97
|
+
end
|
98
|
+
|
99
|
+
def excluded_cluster?(vmh)
|
100
|
+
return unless Berta::Settings.exclude.clusters
|
101
|
+
vmcid = latest_cluster_id(vmh)
|
102
|
+
vmcluster = clusters.find { |cluster| cluster['ID'] == vmcid }
|
103
|
+
return unless vmcluster
|
104
|
+
Berta::Settings.exclude.clusters.find { |name| vmcluster['NAME'] == name } \
|
105
|
+
if vmcluster['NAME']
|
106
|
+
end
|
107
|
+
|
108
|
+
def latest_cluster_id(vmh)
|
109
|
+
vmh.handle['HISTORY_RECORDS/HISTORY[last()]/CID']
|
110
|
+
end
|
111
|
+
|
112
|
+
def takes_resources?(vmh)
|
113
|
+
return true if RESOURCE_STATES.any? { |state| vmh.handle.state_str == state }
|
114
|
+
return true if vmh.handle.state_str == ACTIVE_STATE &&
|
115
|
+
NON_RESOURCE_ACTIVE_LCM_STATES.none? { |state| vmh.handle.lcm_state_str.include? state }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'settingslogic'
|
2
|
+
require 'chronic_duration'
|
3
|
+
|
4
|
+
module Berta
|
5
|
+
# Class for storing setting for Berta
|
6
|
+
class Settings < Settingslogic
|
7
|
+
CONFIGURATION = 'berta.yml'.freeze
|
8
|
+
|
9
|
+
source "#{ENV['HOME']}/.berta/#{CONFIGURATION}"\
|
10
|
+
if File.exist?("#{ENV['HOME']}/.berta/#{CONFIGURATION}")
|
11
|
+
|
12
|
+
source "/etc/berta/#{CONFIGURATION}"\
|
13
|
+
if File.exist?("/etc/berta/#{CONFIGURATION}")
|
14
|
+
|
15
|
+
source "#{File.dirname(__FILE__)}/../../config/#{CONFIGURATION}"
|
16
|
+
|
17
|
+
namespace 'berta'
|
18
|
+
|
19
|
+
# Notification deadline can be written in settings file in human
|
20
|
+
# readable form. This function will return notification deadline
|
21
|
+
# value as integer.
|
22
|
+
#
|
23
|
+
# @return [Numeric] Notification deadline
|
24
|
+
def self.notification_deadline
|
25
|
+
ChronicDuration.parse(get('notification.deadline'))
|
26
|
+
end
|
27
|
+
|
28
|
+
# Expiration offset can be written in settings file in human
|
29
|
+
# readable form. This function will return expiration offset
|
30
|
+
# value as integer.
|
31
|
+
#
|
32
|
+
# @return [Numeric] Expiration offset
|
33
|
+
def self.expiration_offset
|
34
|
+
ChronicDuration.parse(get('expiration.offset'))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/berta/utils.rb
ADDED