cloudkeeper-one 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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +48 -0
  6. data/.travis.yml +22 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +17 -0
  10. data/README.md +98 -0
  11. data/Rakefile +17 -0
  12. data/bin/cloudkeeper-one +5 -0
  13. data/cloudkeeper-one.gemspec +44 -0
  14. data/config/cloudkeeper-one.yml +23 -0
  15. data/config/templates/image.erb +20 -0
  16. data/config/templates/template.erb +19 -0
  17. data/lib/cloudkeeper/one/appliance_actions/list.rb +68 -0
  18. data/lib/cloudkeeper/one/appliance_actions/registration.rb +73 -0
  19. data/lib/cloudkeeper/one/appliance_actions/removal.rb +57 -0
  20. data/lib/cloudkeeper/one/appliance_actions/update.rb +34 -0
  21. data/lib/cloudkeeper/one/appliance_actions/utils/image_download.rb +43 -0
  22. data/lib/cloudkeeper/one/appliance_actions/utils/template_preparation.rb +36 -0
  23. data/lib/cloudkeeper/one/appliance_actions/utils/templates/attributes.erb +23 -0
  24. data/lib/cloudkeeper/one/appliance_actions/utils.rb +10 -0
  25. data/lib/cloudkeeper/one/appliance_actions.rb +11 -0
  26. data/lib/cloudkeeper/one/cli.rb +185 -0
  27. data/lib/cloudkeeper/one/core_connector.rb +106 -0
  28. data/lib/cloudkeeper/one/errors/actions/action_error.rb +9 -0
  29. data/lib/cloudkeeper/one/errors/actions/listing_error.rb +9 -0
  30. data/lib/cloudkeeper/one/errors/actions/registration_error.rb +9 -0
  31. data/lib/cloudkeeper/one/errors/actions/update_error.rb +9 -0
  32. data/lib/cloudkeeper/one/errors/actions.rb +12 -0
  33. data/lib/cloudkeeper/one/errors/argument_error.rb +7 -0
  34. data/lib/cloudkeeper/one/errors/invalid_configuration_error.rb +7 -0
  35. data/lib/cloudkeeper/one/errors/multi_error.rb +25 -0
  36. data/lib/cloudkeeper/one/errors/network_connection_error.rb +7 -0
  37. data/lib/cloudkeeper/one/errors/opennebula/api_call_timeout_error.rb +9 -0
  38. data/lib/cloudkeeper/one/errors/opennebula/authentication_error.rb +9 -0
  39. data/lib/cloudkeeper/one/errors/opennebula/missing_pool_error.rb +9 -0
  40. data/lib/cloudkeeper/one/errors/opennebula/opennebula_error.rb +9 -0
  41. data/lib/cloudkeeper/one/errors/opennebula/resource_not_found_error.rb +9 -0
  42. data/lib/cloudkeeper/one/errors/opennebula/resource_retrieval_error.rb +9 -0
  43. data/lib/cloudkeeper/one/errors/opennebula/resource_state_error.rb +9 -0
  44. data/lib/cloudkeeper/one/errors/opennebula/stub_error.rb +9 -0
  45. data/lib/cloudkeeper/one/errors/opennebula/user_not_authorized_error.rb +9 -0
  46. data/lib/cloudkeeper/one/errors/opennebula.rb +17 -0
  47. data/lib/cloudkeeper/one/errors/standard_error.rb +7 -0
  48. data/lib/cloudkeeper/one/errors.rb +14 -0
  49. data/lib/cloudkeeper/one/opennebula/appliance_handler.rb +91 -0
  50. data/lib/cloudkeeper/one/opennebula/datastore_handler.rb +16 -0
  51. data/lib/cloudkeeper/one/opennebula/group_handler.rb +12 -0
  52. data/lib/cloudkeeper/one/opennebula/handler.rb +59 -0
  53. data/lib/cloudkeeper/one/opennebula/helper.rb +27 -0
  54. data/lib/cloudkeeper/one/opennebula/image_handler.rb +133 -0
  55. data/lib/cloudkeeper/one/opennebula/tags.rb +34 -0
  56. data/lib/cloudkeeper/one/opennebula/template_handler.rb +24 -0
  57. data/lib/cloudkeeper/one/opennebula.rb +14 -0
  58. data/lib/cloudkeeper/one/settings.rb +21 -0
  59. data/lib/cloudkeeper/one/version.rb +5 -0
  60. data/lib/cloudkeeper/one.rb +19 -0
  61. data/lib/cloudkeeper_grpc.rb +5 -0
  62. metadata +385 -0
@@ -0,0 +1,14 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Errors
4
+ autoload :StandardError, 'cloudkeeper/one/errors/standard_error'
5
+ autoload :ArgumentError, 'cloudkeeper/one/errors/argument_error'
6
+ autoload :NetworkConnectionError, 'cloudkeeper/one/errors/network_connection_error'
7
+ autoload :MultiError, 'cloudkeeper/one/errors/multi_error'
8
+ autoload :InvalidConfigurationError, 'cloudkeeper/one/errors/invalid_configuration_error'
9
+
10
+ autoload :Opennebula, 'cloudkeeper/one/errors/opennebula'
11
+ autoload :Actions, 'cloudkeeper/one/errors/actions'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,91 @@
1
+ require 'timeout'
2
+
3
+ module Cloudkeeper
4
+ module One
5
+ module Opennebula
6
+ class ApplianceHandler < Handler
7
+ attr_reader :identifier
8
+
9
+ LEAVE_ID_AS_IS = -1
10
+ ONEADMIN_ID = 0
11
+
12
+ def initialize
13
+ super
14
+
15
+ @identifier = Cloudkeeper::One::Settings[:identifier]
16
+ end
17
+
18
+ def find_by_appliance_id(appliance_id)
19
+ find_all_by "TEMPLATE/#{Tags::APPLIANCE_ID}" => appliance_id
20
+ end
21
+
22
+ def find_by_image_list_id(image_list_id)
23
+ find_all_by "TEMPLATE/#{Tags::APPLIANCE_IMAGE_LIST_ID}" => image_list_id
24
+ end
25
+
26
+ def delete(element)
27
+ raise Cloudkeeper::One::Errors::ArgumentError, 'element cannot be nil' unless element
28
+
29
+ id = element.id
30
+ handle_opennebula_error { element.delete }
31
+
32
+ timeout { sleep(Cloudkeeper::One::Opennebula::Handler::API_POLLING_WAIT) while exist? id }
33
+ end
34
+
35
+ def chmod(element, permissions)
36
+ raise Cloudkeeper::One::Errors::ArgumentError, 'element cannot be nil' unless element
37
+
38
+ handle_opennebula_error do
39
+ element.chmod_octet permissions
40
+ element.info!
41
+ end
42
+ end
43
+
44
+ def update(element, template)
45
+ raise Cloudkeeper::One::Errors::ArgumentError, 'element cannot be nil' unless element
46
+
47
+ handle_opennebula_error do
48
+ element.update template, true
49
+ element.info!
50
+ end
51
+ end
52
+
53
+ def chgrp(element, group)
54
+ raise Cloudkeeper::One::Errors::ArgumentError, 'element cannot be nil' unless element
55
+
56
+ handle_opennebula_error { element.info! }
57
+ return if group.id == element.gid
58
+
59
+ handle_opennebula_error do
60
+ element.chown(LEAVE_ID_AS_IS, group.id)
61
+ element.info!
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def find_all_by(attributes)
68
+ xpaths = attributes.clone
69
+ xpaths['UNAME'] = Cloudkeeper::One::Settings[:'opennebula-users'] \
70
+ if Cloudkeeper::One::Settings[:'opennebula-users'] && !Cloudkeeper::One::Settings[:'opennebula-users'].empty?
71
+
72
+ find_all xpaths
73
+ end
74
+
75
+ def timeout
76
+ raise Cloudkeeper::One::Errors::ArgumentError, 'Timeout called without a block!' unless block_given?
77
+
78
+ Timeout.timeout(Cloudkeeper::One::Settings[:'opennebula-api-call-timeout']) { yield }
79
+ rescue Timeout::Error
80
+ raise Cloudkeeper::One::Errors::Opennebula::ApiCallTimeoutError, 'Operation was not successful within the timeout'
81
+ end
82
+
83
+ def find(method, xpaths = {})
84
+ reload!
85
+
86
+ pool.send(method) { |element| element["TEMPLATE/#{Tags::ID}"] == identifier && evaluate_xpaths(element, xpaths) }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,16 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ class DatastoreHandler < Handler
5
+ def initialize
6
+ super
7
+ @pool = OpenNebula::DatastorePool.new client
8
+ end
9
+
10
+ def find_by_names(names)
11
+ names.map { |name| find_by_name name }.compact
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ class GroupHandler < Handler
5
+ def initialize
6
+ super
7
+ @pool = OpenNebula::GroupPool.new client
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,59 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ class Handler
5
+ include Helper
6
+ attr_reader :client
7
+ attr_accessor :pool
8
+
9
+ API_POLLING_WAIT = 5
10
+
11
+ def initialize
12
+ @client = OpenNebula::Client.new Cloudkeeper::One::Settings[:'opennebula-secret'],
13
+ Cloudkeeper::One::Settings[:'opennebula-endpoint']
14
+ end
15
+
16
+ def find_one(xpaths = {})
17
+ find(:find, xpaths)
18
+ end
19
+
20
+ def find_all(xpaths = {})
21
+ find(:find_all, xpaths)
22
+ end
23
+
24
+ def find_by_name(name)
25
+ find_one('NAME' => name)
26
+ end
27
+
28
+ def find_by_id(id)
29
+ find_one('ID' => id.to_s)
30
+ end
31
+
32
+ def exist?(id)
33
+ find_by_id id
34
+ end
35
+
36
+ private
37
+
38
+ def find(method, xpaths = {})
39
+ reload!
40
+
41
+ pool.send(method) { |element| evaluate_xpaths(element, xpaths) }
42
+ end
43
+
44
+ def evaluate_xpaths(element, xpaths)
45
+ xpaths.inject(true) do |red, elem|
46
+ red && (elem.last.is_a?(Array) ? elem.last.include?(element[elem.first]) : element[elem.first] == elem.last)
47
+ end
48
+ end
49
+
50
+ def reload!
51
+ raise Cloudkeeper::One::Errors::Opennebula::MissingPoolError, 'Handler is missing an OpenNebula pool' unless pool
52
+
53
+ method = pool.respond_to?('info_all!') ? 'info_all!' : 'info!'
54
+ handle_opennebula_error { pool.send(method.to_sym) }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ module Helper
5
+ ERRORS = Hash.new(Cloudkeeper::One::Errors::Opennebula::ResourceRetrievalError)
6
+ .update(::OpenNebula::Error::EAUTHENTICATION => Cloudkeeper::One::Errors::Opennebula::AuthenticationError,
7
+ ::OpenNebula::Error::EAUTHORIZATION => Cloudkeeper::One::Errors::Opennebula::UserNotAuthorizedError,
8
+ ::OpenNebula::Error::ENO_EXISTS => Cloudkeeper::One::Errors::Opennebula::ResourceNotFoundError,
9
+ ::OpenNebula::Error::EACTION => Cloudkeeper::One::Errors::Opennebula::ResourceStateError).freeze
10
+
11
+ def handle_opennebula_error
12
+ raise Cloudkeeper::One::Errors::ArgumentError, 'OpenNebula service-wrapper was called without a block!' \
13
+ unless block_given?
14
+
15
+ return_value = yield
16
+ return return_value unless OpenNebula.is_error?(return_value)
17
+
18
+ raise decode_error(return_value.errno), return_value.message
19
+ end
20
+
21
+ def decode_error(errno)
22
+ ERRORS[errno]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,133 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ class ImageHandler < ApplianceHandler
5
+ IMAGE_STATES = {
6
+ ready: 'READY',
7
+ used: 'USED',
8
+ disabled: 'DISABLED',
9
+ error: 'ERROR'
10
+ }.freeze
11
+
12
+ EXPIRED_PERMISSIONS = '600'.freeze
13
+
14
+ def initialize
15
+ super
16
+ @pool = OpenNebula::ImagePool.new client
17
+ end
18
+
19
+ def expired
20
+ xpaths = { "TEMPLATE/#{Tags::EXPIRED}" => 'yes' }
21
+ xpaths['UNAME'] = Cloudkeeper::One::Settings[:'opennebula-users'] \
22
+ if Cloudkeeper::One::Settings[:'opennebula-users'] && !Cloudkeeper::One::Settings[:'opennebula-users'].empty?
23
+
24
+ find_all xpaths
25
+ end
26
+
27
+ def delete(image)
28
+ raise Cloudkeeper::One::Errors::ArgumentError, 'image cannot be nil' unless image
29
+
30
+ id = image.id
31
+
32
+ if used? image
33
+ logger.warn "Image with id #{id.inspect} cannot be removed, still in use"
34
+ return
35
+ end
36
+
37
+ super image
38
+ end
39
+
40
+ def disable(image)
41
+ raise Cloudkeeper::One::Errors::ArgumentError, 'image cannot be nil' unless image
42
+
43
+ id = image.id
44
+
45
+ if disabled? image
46
+ logger.info "Image with id #{id.inspect} is already disabled, skipping"
47
+ return
48
+ end
49
+
50
+ unless free? image
51
+ logger.warn "Image with id #{id.inspect} cannot be disabled"
52
+ return
53
+ end
54
+
55
+ handle_opennebula_error { image.disable }
56
+
57
+ timeout { sleep(Cloudkeeper::One::Opennebula::Handler::API_POLLING_WAIT) until disabled? image }
58
+ end
59
+
60
+ def expire(image)
61
+ raise Cloudkeeper::One::Errors::ArgumentError, 'image cannot be nil' unless image
62
+
63
+ id = image.id
64
+
65
+ if expired? image
66
+ logger.debug("Image with id #{id.inspect} is already expired, skipping")
67
+ return
68
+ end
69
+
70
+ chmod image, EXPIRED_PERMISSIONS
71
+ disable image
72
+
73
+ expiration_attribute = "#{Tags::EXPIRED} = \"yes\""
74
+
75
+ handle_opennebula_error { image.rename("EXPIRED_#{Time.now.to_i}_#{image.name}") }
76
+ handle_opennebula_error { image.update(expiration_attribute, true) }
77
+ end
78
+
79
+ def register(image_template, datastore, group)
80
+ image_alloc = OpenNebula::Image.build_xml
81
+ image = OpenNebula::Image.new(image_alloc, client)
82
+
83
+ handle_opennebula_error { image.allocate(image_template, datastore.id) }
84
+
85
+ timeout do
86
+ until ready? image
87
+ raise Cloudkeeper::One::Errors::Opennebula::ResourceStateError, image['TEMPLATE/ERROR'] if error? image
88
+ sleep(Cloudkeeper::One::Opennebula::Handler::API_POLLING_WAIT)
89
+ end
90
+ end
91
+
92
+ chmod image, Cloudkeeper::One::Settings[:'appliances-permissions']
93
+ chgrp image, group
94
+
95
+ image
96
+ end
97
+
98
+ def expired?(image)
99
+ is?(image) { image["TEMPLATE/#{Tags::EXPIRED}"] == 'yes' }
100
+ end
101
+
102
+ def disabled?(image)
103
+ is?(image) { image.state_str == IMAGE_STATES[:disabled] }
104
+ end
105
+
106
+ def ready?(image)
107
+ is?(image) { image.state_str == IMAGE_STATES[:ready] }
108
+ end
109
+
110
+ def used?(image)
111
+ is?(image) { image.state_str == IMAGE_STATES[:used] }
112
+ end
113
+
114
+ def free?(image)
115
+ is?(image) { image.state_str == IMAGE_STATES[:ready] || image.state_str == IMAGE_STATES[:error] }
116
+ end
117
+
118
+ def error?(image)
119
+ is?(image) { image.state_str == IMAGE_STATES[:error] }
120
+ end
121
+
122
+ private
123
+
124
+ def is?(image)
125
+ raise Cloudkeeper::One::Errors::ArgumentError, 'image cannot be nil' unless image
126
+
127
+ handle_opennebula_error { image.info! }
128
+ yield
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,34 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ module Tags
5
+ BASE = 'CLOUDKEEPER'.freeze
6
+ APPLIANCE = "#{BASE}_APPLIANCE".freeze
7
+ IMAGE = "#{BASE}_IMAGE".freeze
8
+
9
+ ID = "#{BASE}_ID".freeze
10
+ EXPIRED = "#{BASE}_EXPIRED".freeze
11
+
12
+ APPLIANCE_ID = "#{APPLIANCE}_ID".freeze
13
+ APPLIANCE_TITLE = "#{APPLIANCE}_TITLE".freeze
14
+ APPLIANCE_DESCRIPTION = "#{APPLIANCE}_DESCRIPTION".freeze
15
+ APPLIANCE_MPURI = "#{APPLIANCE}_MPURI".freeze
16
+ APPLIANCE_GROUP = "#{APPLIANCE}_GROUP".freeze
17
+ APPLIANCE_RAM = "#{APPLIANCE}_RAM".freeze
18
+ APPLIANCE_CORE = "#{APPLIANCE}_CORE".freeze
19
+ APPLIANCE_VERSION = "#{APPLIANCE}_VERSION".freeze
20
+ APPLIANCE_ARCHITECTURE = "#{APPLIANCE}_ARCHITECTURE".freeze
21
+ APPLIANCE_OPERATING_SYSTEM = "#{APPLIANCE}_OPERATING_SYSTEM".freeze
22
+ APPLIANCE_VO = "#{APPLIANCE}_VO".freeze
23
+ APPLIANCE_EXPIRATION_DATE = "#{APPLIANCE}_EXPIRATION_DATE".freeze
24
+ APPLIANCE_IMAGE_LIST_ID = "#{APPLIANCE}_IMAGE_LIST_ID".freeze
25
+ APPLIANCE_ATTRIBUTES = "#{APPLIANCE}_ATTRIBUTES".freeze
26
+
27
+ IMAGE_URI = "#{IMAGE}_URI".freeze
28
+ IMAGE_CHECKSUM = "#{IMAGE}_CHECKSUM".freeze
29
+ IMAGE_SIZE = "#{IMAGE}_SIZE".freeze
30
+ IMAGE_FORMAT = "#{IMAGE}_FORMAT".freeze
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ class TemplateHandler < ApplianceHandler
5
+ def initialize
6
+ super
7
+ @pool = OpenNebula::TemplatePool.new client
8
+ end
9
+
10
+ def register(template_template, group)
11
+ template_alloc = OpenNebula::Template.build_xml
12
+ template = OpenNebula::Template.new(template_alloc, client)
13
+
14
+ handle_opennebula_error { template.allocate template_template }
15
+
16
+ chmod template, Cloudkeeper::One::Settings[:'appliances-permissions']
17
+ chgrp template, group
18
+
19
+ template
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module Cloudkeeper
2
+ module One
3
+ module Opennebula
4
+ autoload :Helper, 'cloudkeeper/one/opennebula/helper'
5
+ autoload :Tags, 'cloudkeeper/one/opennebula/tags'
6
+ autoload :Handler, 'cloudkeeper/one/opennebula/handler'
7
+ autoload :DatastoreHandler, 'cloudkeeper/one/opennebula/datastore_handler'
8
+ autoload :GroupHandler, 'cloudkeeper/one/opennebula/group_handler'
9
+ autoload :ApplianceHandler, 'cloudkeeper/one/opennebula/appliance_handler'
10
+ autoload :ImageHandler, 'cloudkeeper/one/opennebula/image_handler'
11
+ autoload :TemplateHandler, 'cloudkeeper/one/opennebula/template_handler'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ require 'settingslogic'
2
+
3
+ module Cloudkeeper
4
+ module One
5
+ class Settings < Settingslogic
6
+ CONFIGURATION = 'cloudkeeper-one.yml'.freeze
7
+
8
+ # three possible configuration file locations in order by preference
9
+ # if configuration file is found rest of the locations are ignored
10
+ source "#{ENV['HOME']}/.cloudkeeper-one/#{CONFIGURATION}"\
11
+ if File.exist?("#{ENV['HOME']}/.cloudkeeper-one/#{CONFIGURATION}")
12
+
13
+ source "/etc/cloudkeeper-one/#{CONFIGURATION}"\
14
+ if File.exist?("/etc/cloudkeeper-one/#{CONFIGURATION}")
15
+
16
+ source "#{File.dirname(__FILE__)}/../../../config/#{CONFIGURATION}"
17
+
18
+ namespace 'cloudkeeper-one'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module Cloudkeeper
2
+ module One
3
+ VERSION = '1.0.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_support/all'
2
+ require 'opennebula'
3
+ require 'json'
4
+ require 'base64'
5
+ require 'tempfile'
6
+
7
+ module Cloudkeeper
8
+ module One
9
+ autoload :Errors, 'cloudkeeper/one/errors'
10
+ autoload :Opennebula, 'cloudkeeper/one/opennebula'
11
+ autoload :ApplianceActions, 'cloudkeeper/one/appliance_actions'
12
+
13
+ autoload :CLI, 'cloudkeeper/one/cli'
14
+ autoload :Settings, 'cloudkeeper/one/settings'
15
+ autoload :CoreConnector, 'cloudkeeper/one/core_connector'
16
+ end
17
+ end
18
+
19
+ require 'cloudkeeper/one/version'
@@ -0,0 +1,5 @@
1
+ module CloudkeeperGrpc
2
+ require 'cloudkeeper_grpc/cloudkeeper_pb'
3
+ require 'cloudkeeper_grpc/cloudkeeper_services_pb'
4
+ require 'cloudkeeper_grpc/constants'
5
+ end