berta 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +26 -0
  3. data/.travis.yml +19 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +14 -0
  6. data/README.md +48 -0
  7. data/Rakefile +17 -0
  8. data/berta.gemspec +32 -0
  9. data/bin/berta +4 -0
  10. data/config/berta.yml +19 -0
  11. data/config/email.erb +18 -0
  12. data/lib/berta.rb +13 -0
  13. data/lib/berta/cli.rb +108 -0
  14. data/lib/berta/command_executor.rb +18 -0
  15. data/lib/berta/entities.rb +6 -0
  16. data/lib/berta/entities/expiration.rb +82 -0
  17. data/lib/berta/errors.rb +9 -0
  18. data/lib/berta/errors/backend_error.rb +5 -0
  19. data/lib/berta/errors/entities.rb +9 -0
  20. data/lib/berta/errors/entities/invalid_entity_xml_error.rb +7 -0
  21. data/lib/berta/errors/entities/no_user_email_error.rb +7 -0
  22. data/lib/berta/errors/opennebula.rb +13 -0
  23. data/lib/berta/errors/opennebula/authentication_error.rb +7 -0
  24. data/lib/berta/errors/opennebula/resource_not_found_error.rb +7 -0
  25. data/lib/berta/errors/opennebula/resource_retrieval_error.rb +7 -0
  26. data/lib/berta/errors/opennebula/resource_state_error.rb +7 -0
  27. data/lib/berta/errors/opennebula/stub_error.rb +7 -0
  28. data/lib/berta/errors/opennebula/user_not_authorized_error.rb +7 -0
  29. data/lib/berta/errors/standard_error.rb +5 -0
  30. data/lib/berta/expiration_manager.rb +42 -0
  31. data/lib/berta/notification_manager.rb +100 -0
  32. data/lib/berta/service.rb +118 -0
  33. data/lib/berta/settings.rb +37 -0
  34. data/lib/berta/utils.rb +6 -0
  35. data/lib/berta/utils/opennebula.rb +8 -0
  36. data/lib/berta/utils/opennebula/helper.rb +47 -0
  37. data/lib/berta/virtual_machine_handler.rb +120 -0
  38. 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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
+ [![Build Status](https://travis-ci.org/dudoslav/berta.svg?branch=master)](https://travis-ci.org/dudoslav/berta)
4
+ [![Coverage Status](https://coveralls.io/repos/github/dudoslav/berta/badge.svg?branch=master)](https://coveralls.io/github/dudoslav/berta?branch=master)
5
+ [![Code Climate](https://codeclimate.com/github/dudoslav/berta/badges/gpa.svg)](https://codeclimate.com/github/dudoslav/berta)
6
+ [![Inline docs](http://inch-ci.org/github/dudoslav/berta.svg?branch=master)](http://inch-ci.org/github/dudoslav/berta)
7
+ [![Dependency Status](https://gemnasium.com/badges/github.com/dudoslav/berta.svg)](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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'berta'
3
+
4
+ Berta::CLI.start(ARGV)
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,6 @@
1
+ module Berta
2
+ # Module for entity classes
3
+ module Entities
4
+ autoload :Expiration, 'berta/entities/expiration.rb'
5
+ end
6
+ 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