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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c35df5b38eb1ec48f721aab12ab14ae6006f32d
|
4
|
+
data.tar.gz: 205b376e2b57fb9738880bdd47817b4933135f04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9500f02e7755d6eab251cb337bf397cb9979f685fc13b736d433756289c3a5da0ba358cd4731f7e58eef955144f93aff8c8598f5e59cceba148a168cb472811a
|
7
|
+
data.tar.gz: 3581efd6664b2f26394e7e0bc752c22c01d7e983ea5d113e026044a3df7d65186e34dc305d992ad5accbe1720f75edd8ae110647b6fa795ffa0c77780b6ba466
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.0
|
5
|
+
|
6
|
+
Metrics/LineLength:
|
7
|
+
Max: 135
|
8
|
+
|
9
|
+
Metrics/MethodLength:
|
10
|
+
Max: 15
|
11
|
+
|
12
|
+
Metrics/BlockLength:
|
13
|
+
Exclude:
|
14
|
+
- 'Rakefile'
|
15
|
+
- '**/*.rake'
|
16
|
+
- 'spec/**/*.rb'
|
17
|
+
- '*.gemspec'
|
18
|
+
Metrics/AbcSize:
|
19
|
+
Exclude:
|
20
|
+
- 'lib/berta/cli.rb'
|
21
|
+
|
22
|
+
RSpec/MultipleExpectations:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
RSpec/ExampleLength:
|
26
|
+
Enabled: false
|
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
sudo: false
|
2
|
+
|
3
|
+
language: ruby
|
4
|
+
rvm:
|
5
|
+
- ruby-head
|
6
|
+
- 2.2.0
|
7
|
+
|
8
|
+
matrix:
|
9
|
+
allow_failures:
|
10
|
+
- rvm: ruby-head
|
11
|
+
fast_finish: true
|
12
|
+
|
13
|
+
branches:
|
14
|
+
only:
|
15
|
+
- master
|
16
|
+
|
17
|
+
before_install: 'gem install bundler -v 1.13.0'
|
18
|
+
|
19
|
+
script: 'bundle exec rake acceptance'
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
Copyright 2016 Dusan Baran
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
you may not use this file except in compliance with the License.
|
6
|
+
You may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
See the License for the specific language governing permissions and
|
14
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Berta
|
2
|
+
|
3
|
+
[](https://travis-ci.org/dudoslav/berta)
|
4
|
+
[](https://coveralls.io/github/dudoslav/berta?branch=master)
|
5
|
+
[](https://codeclimate.com/github/dudoslav/berta)
|
6
|
+
[](http://inch-ci.org/github/dudoslav/berta)
|
7
|
+
[](https://gemnasium.com/github.com/dudoslav/berta)
|
8
|
+
|
9
|
+
Berta cleans cloud from unused vms. She sets expiration to all virtual machines
|
10
|
+
and when expiration is close she will notify owners. Berta is developed as ruby gem.
|
11
|
+
|
12
|
+
## Getting started
|
13
|
+
|
14
|
+
### Installation
|
15
|
+
|
16
|
+
From rubygems:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
gem install berta
|
20
|
+
```
|
21
|
+
|
22
|
+
From source:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
git clone git://github.com/dudoslav/berta.git
|
26
|
+
cd cloudkeeper
|
27
|
+
gem install bundler
|
28
|
+
bundle install
|
29
|
+
```
|
30
|
+
|
31
|
+
### Configuration
|
32
|
+
|
33
|
+
Config files can be located in:
|
34
|
+
* `~/.berta/berta.yml`
|
35
|
+
* `/etc/berta/berta.yml`
|
36
|
+
* `PATH_TO_GEM_DIR/config/cloudkeeper.yml`
|
37
|
+
|
38
|
+
### Execution
|
39
|
+
|
40
|
+
Berta needs access to opennebula backend. To do that she needs to know opennebula
|
41
|
+
secret and endpoint. This can be specified in config file, as command line options or
|
42
|
+
by creating `one_auth` file in `~/.one`.
|
43
|
+
To run berta simply type:
|
44
|
+
```bash
|
45
|
+
berta
|
46
|
+
# or if backend and secret are not set
|
47
|
+
berta --opennebula-secret=<secret> --opennebula-endpoint=<endpoint>
|
48
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
RuboCop::RakeTask.new
|
7
|
+
|
8
|
+
task default: :test
|
9
|
+
|
10
|
+
desc 'Run acceptance tests (RSpec + Rubocop)'
|
11
|
+
task test: 'acceptance'
|
12
|
+
|
13
|
+
desc 'Run acceptance tests (RSpec + Rubocop)'
|
14
|
+
task :acceptance do |_t|
|
15
|
+
Rake::Task['spec'].invoke
|
16
|
+
Rake::Task['rubocop'].invoke
|
17
|
+
end
|
data/berta.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'berta'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.summary = 'Berta VM expiration tool'
|
5
|
+
s.description = 'Berta will check all VMs on OpenNebula cloud for expiration date'
|
6
|
+
s.authors = ['Dusan Baran']
|
7
|
+
s.email = 'dbaran@hotmail.sk'
|
8
|
+
s.homepage = 'https://github.com/dudoslav/berta'
|
9
|
+
s.license = 'Apache License, Version 2.0'
|
10
|
+
|
11
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
12
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
13
|
+
s.require_paths = ['lib']
|
14
|
+
|
15
|
+
s.add_development_dependency 'bundler', '~> 1.13'
|
16
|
+
s.add_development_dependency 'rake', '~> 11.2'
|
17
|
+
s.add_development_dependency 'rspec', '~> 3.5'
|
18
|
+
s.add_development_dependency 'rubocop', '~> 0.42'
|
19
|
+
s.add_development_dependency 'rubocop-rspec', '~> 1.7'
|
20
|
+
s.add_development_dependency 'pry', '~> 0.10'
|
21
|
+
s.add_development_dependency 'webmock', '~> 2.3'
|
22
|
+
s.add_development_dependency 'vcr', '~> 3.0'
|
23
|
+
s.add_development_dependency 'coveralls', '~> 0.8'
|
24
|
+
|
25
|
+
s.add_runtime_dependency 'thor', '~> 0.19'
|
26
|
+
s.add_runtime_dependency 'settingslogic', '~> 2.0'
|
27
|
+
s.add_runtime_dependency 'yell', '~> 2.0'
|
28
|
+
s.add_runtime_dependency 'opennebula', '~> 5.2'
|
29
|
+
s.add_runtime_dependency 'chronic_duration', '~> 0.10'
|
30
|
+
s.add_runtime_dependency 'mail', '~> 2.6'
|
31
|
+
s.add_runtime_dependency 'tilt', '~> 2.0'
|
32
|
+
end
|
data/bin/berta
ADDED
data/config/berta.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
berta:
|
2
|
+
opennebula:
|
3
|
+
secret: # Secret for OpenNebula authentication
|
4
|
+
endpoint: # OpenNebula backend endpoint
|
5
|
+
expiration: # Action that will be set for execution
|
6
|
+
offset: 10 days # Time until action will be executed
|
7
|
+
action: terminate-hard # Type of action that will be executed
|
8
|
+
notification:
|
9
|
+
deadline: 1 day # Time when user should be notified before action will be executed
|
10
|
+
exclude: # Exclude VMs to ignore them
|
11
|
+
ids: # VMs with this IDs will be ignored
|
12
|
+
users: # VMs owned by this users will be ignored
|
13
|
+
groups: # VMs in this groups will be ignored
|
14
|
+
clusters: # VMs in this clusters will be ignored
|
15
|
+
logging:
|
16
|
+
file: /var/log/berta/berta.log # File ro write log to. To turn off file logging leave this field empty
|
17
|
+
level: error # Logging level
|
18
|
+
dry-run: false # Berta wont change anything on running instance of OpenNebula
|
19
|
+
debug: false # Debug mode
|
data/config/email.erb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
<%# Avaliable parametes: %>
|
2
|
+
<%# [String] user_name %>
|
3
|
+
<%# [String] user_email %>
|
4
|
+
<%# [Array<Hash>] vms, Array of Hashes with values: %>
|
5
|
+
<%# [String] id %>
|
6
|
+
<%# [String] name %>
|
7
|
+
<%# [String] expiration %>
|
8
|
+
From: cesnet@mail.muni.cz
|
9
|
+
To: <%= user_email %>
|
10
|
+
Subject: OpenNebula VM expiration
|
11
|
+
|
12
|
+
Hi <%= user_name %>,
|
13
|
+
your vms are about to expire..
|
14
|
+
|
15
|
+
VM Name | Expiration Date
|
16
|
+
<% vms.each do |vm| %>
|
17
|
+
<%= vm[:name] %> | <%= Time.at(vm[:expiration]) %>
|
18
|
+
<% end %>
|
data/lib/berta.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Main Berta module
|
2
|
+
module Berta
|
3
|
+
autoload :Service, 'berta/service'
|
4
|
+
autoload :VirtualMachineHandler, 'berta/virtual_machine_handler'
|
5
|
+
autoload :ExpirationManager, 'berta/expiration_manager'
|
6
|
+
autoload :NotificationManager, 'berta/notification_manager'
|
7
|
+
autoload :CommandExecutor, 'berta/command_executor'
|
8
|
+
autoload :CLI, 'berta/cli'
|
9
|
+
autoload :Errors, 'berta/errors'
|
10
|
+
autoload :Entities, 'berta/entities'
|
11
|
+
autoload :Utils, 'berta/utils'
|
12
|
+
autoload :Settings, 'berta/settings'
|
13
|
+
end
|
data/lib/berta/cli.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yell'
|
3
|
+
|
4
|
+
module Berta
|
5
|
+
# CLI for berta
|
6
|
+
class CLI < Thor
|
7
|
+
def self.safe_fetch(keys)
|
8
|
+
current = Berta::Settings
|
9
|
+
keys.each do |key|
|
10
|
+
current = current[key]
|
11
|
+
break unless current
|
12
|
+
end
|
13
|
+
current
|
14
|
+
end
|
15
|
+
|
16
|
+
class_option :'opennebula-secret',
|
17
|
+
default: safe_fetch(%w(opennebula secret)),
|
18
|
+
type: :string
|
19
|
+
class_option :'opennebula-endpoint',
|
20
|
+
default: safe_fetch(%w(opennebula endpoint)),
|
21
|
+
type: :string
|
22
|
+
class_option :'expiration-offset',
|
23
|
+
required: true,
|
24
|
+
default: safe_fetch(%w(expiration offset)),
|
25
|
+
type: :string
|
26
|
+
class_option :'expiration-action',
|
27
|
+
required: true,
|
28
|
+
default: safe_fetch(%w(expiration action)),
|
29
|
+
type: :string
|
30
|
+
class_option :'notification-deadline',
|
31
|
+
required: true,
|
32
|
+
default: safe_fetch(%w(notification deadline)),
|
33
|
+
type: :string
|
34
|
+
class_option :'exclude-ids',
|
35
|
+
default: safe_fetch(%w(exclude ids)),
|
36
|
+
type: :array
|
37
|
+
class_option :'exclude-users',
|
38
|
+
default: safe_fetch(%w(exclude users)),
|
39
|
+
type: :array
|
40
|
+
class_option :'exclude-groups',
|
41
|
+
default: safe_fetch(%w(exclude groups)),
|
42
|
+
type: :array
|
43
|
+
class_option :'exclude-clusters',
|
44
|
+
default: safe_fetch(%w(exclude clusters)),
|
45
|
+
type: :array
|
46
|
+
class_option :'dry-run',
|
47
|
+
default: safe_fetch(%w(dry-run)),
|
48
|
+
type: :boolean
|
49
|
+
class_option :'logging-file',
|
50
|
+
default: safe_fetch(%w(logging file)),
|
51
|
+
type: :string
|
52
|
+
class_option :'logging-level',
|
53
|
+
required: true,
|
54
|
+
default: safe_fetch(%w(logging level)),
|
55
|
+
type: :string
|
56
|
+
class_option :debug,
|
57
|
+
default: safe_fetch(%w(debug)),
|
58
|
+
type: :boolean
|
59
|
+
|
60
|
+
desc 'cleanup', 'Task that sets all expiration to all vms and notifies users'
|
61
|
+
def cleanup
|
62
|
+
initialize_configuration(options)
|
63
|
+
initialize_logger(options)
|
64
|
+
Berta::CommandExecutor.cleanup
|
65
|
+
end
|
66
|
+
default_task :cleanup
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def initialize_configuration(options)
|
71
|
+
settings = Hash.new { |hash, key| hash[key] = {} }
|
72
|
+
settings['opennebula']['secret'] = options['opennebula-secret']
|
73
|
+
settings['opennebula']['endpoint'] = options['opennebula-endpoint']
|
74
|
+
settings['expiration']['offset'] = options['expiration-offset']
|
75
|
+
settings['expiration']['action'] = options['expiration-action']
|
76
|
+
settings['notification']['deadline'] = options['notification-deadline']
|
77
|
+
settings['exclude']['ids'] = options['exclude-ids']
|
78
|
+
settings['exclude']['users'] = options['exclude-users']
|
79
|
+
settings['exclude']['groups'] = options['exclude-groups']
|
80
|
+
settings['exclude']['clusters'] = options['exclude-clusters']
|
81
|
+
settings['dry-run'] = options['dry-run']
|
82
|
+
settings['debug'] = options['debug']
|
83
|
+
settings['logging']['file'] = options['logging-file']
|
84
|
+
settings['logging']['level'] = options['logging-level']
|
85
|
+
Berta::Settings.merge!(settings)
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize_logger(options)
|
89
|
+
logging_level = options['logging-level']
|
90
|
+
logging_level = 'debug' if options['debug'] || options['dry-run']
|
91
|
+
|
92
|
+
Yell.new :stdout, name: Object, level: logging_level, format: Yell::DefaultFormat
|
93
|
+
Object.send :include, Yell::Loggable
|
94
|
+
|
95
|
+
setup_file_logger(options['logging-file']) if options['logging-file']
|
96
|
+
|
97
|
+
logger.debug 'Running in debug mode...'
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup_file_logger(logging_file)
|
101
|
+
unless (File.exist?(logging_file) && File.writable?(logging_file)) || File.writable?(File.dirname(logging_file))
|
102
|
+
logger.error "File #{logging_file} isn't writable"
|
103
|
+
return
|
104
|
+
end
|
105
|
+
logger.adapter :file, logging_file
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Berta
|
2
|
+
# Class for executing main berta commands
|
3
|
+
class CommandExecutor
|
4
|
+
# Function that performs clean up operation.
|
5
|
+
# Connects to opennebula database,
|
6
|
+
# runs expiration update process and
|
7
|
+
# notifies users about upcoming expirations.
|
8
|
+
def self.cleanup
|
9
|
+
service = Berta::Service.new(Berta::Settings['opennebula']['secret'],
|
10
|
+
Berta::Settings['opennebula']['endpoint'])
|
11
|
+
vms = service.running_vms
|
12
|
+
Berta::ExpirationManager.new.update_expirations(vms)
|
13
|
+
Berta::NotificationManager.new(service).notify_users(vms)
|
14
|
+
rescue Berta::Errors::BackendError => e
|
15
|
+
logger.error e.message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Berta
|
2
|
+
module Entities
|
3
|
+
# Class for storing expiration data, also can be created from xml
|
4
|
+
class Expiration
|
5
|
+
attr_reader :id, :time, :action
|
6
|
+
|
7
|
+
# Creates Expiration class instance from XMLElement.
|
8
|
+
#
|
9
|
+
# @param xml [XMLElement] XML from which to create instance
|
10
|
+
# @return [Berta::Entities::Expiration] Expiration from given xml
|
11
|
+
# @raise [Berta::Errors::Entities::InvalidEntityXMLError] If XMLElement was not in correct format
|
12
|
+
def self.from_xml(xml)
|
13
|
+
check_xml!(xml)
|
14
|
+
Berta::Entities::Expiration.new(xml['ID'], xml['TIME'], xml['ACTION'])
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raises error if XML element is not in correct format.
|
18
|
+
#
|
19
|
+
# @param xml [XMLElement] XML to check for all required values
|
20
|
+
# @raise [Berta::Errors::Entities::InvalidEntityXMLError] If xml is not in correct format
|
21
|
+
def self.check_xml!(xml)
|
22
|
+
raise Berta::Errors::Entities::InvalidEntityXMLError, 'wrong enxpiration xml recieved' \
|
23
|
+
unless %w(ID
|
24
|
+
ACTION
|
25
|
+
TIME).all? { |path| xml.has_elements? path }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates expiration class instance from given arguments.
|
29
|
+
#
|
30
|
+
# @param id [String] Schelude action id
|
31
|
+
# @param time [String] Schelude action execution time
|
32
|
+
# @param action [String] Schelude action
|
33
|
+
def initialize(id, time, action)
|
34
|
+
@id = id
|
35
|
+
@time = time
|
36
|
+
@action = action
|
37
|
+
end
|
38
|
+
|
39
|
+
# Generate schelude action template that can be used
|
40
|
+
# in VMS USER_TEMPLATE/SCHED_ACTION
|
41
|
+
#
|
42
|
+
# @return [String] Schelude action template
|
43
|
+
def template
|
44
|
+
<<-EOT
|
45
|
+
SCHED_ACTION = [
|
46
|
+
ID = "#{id}",
|
47
|
+
ACTION = "#{action}",
|
48
|
+
TIME = "#{time}"
|
49
|
+
]
|
50
|
+
EOT
|
51
|
+
end
|
52
|
+
|
53
|
+
# Determines if this schelude action is in notification interval.
|
54
|
+
# That means its expiration is closer to Time.now than notification
|
55
|
+
# deadline.
|
56
|
+
#
|
57
|
+
# @return [Boolean] Truthy if in notification interval else falsy
|
58
|
+
def in_notification_interval?
|
59
|
+
time_interval = time.to_i - Time.now.to_i
|
60
|
+
time_interval <= Berta::Settings.notification_deadline && time_interval >= 0
|
61
|
+
end
|
62
|
+
|
63
|
+
# Determines if this schelude action is in expiration interval.
|
64
|
+
# That means its expiration is closer to Time.now that expiration
|
65
|
+
# offset.
|
66
|
+
#
|
67
|
+
# @return [Boolean] Truthy if in expiration interval else falsy
|
68
|
+
def in_expiration_interval?
|
69
|
+
time_interval = time.to_i - Time.now.to_i
|
70
|
+
time_interval <= Berta::Settings.expiration_offset && time_interval >= 0
|
71
|
+
end
|
72
|
+
|
73
|
+
# Determines if this expiration has default expiration action set.
|
74
|
+
# Default expiration action is action defined in settings file.
|
75
|
+
#
|
76
|
+
# @return [Boolean] Truthy if has default action else falsy
|
77
|
+
def default_action?
|
78
|
+
action == Berta::Settings.expiration.action
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|