misty 0.1.0 → 0.3.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +222 -1
  3. data/lib/misty.rb +8 -0
  4. data/lib/misty/auth.rb +60 -0
  5. data/lib/misty/auth/auth_v2.rb +46 -0
  6. data/lib/misty/auth/auth_v3.rb +58 -0
  7. data/lib/misty/autoload.rb +82 -0
  8. data/lib/misty/cloud.rb +133 -0
  9. data/lib/misty/http/client.rb +115 -0
  10. data/lib/misty/http/direct.rb +26 -0
  11. data/lib/misty/http/method_builder.rb +96 -0
  12. data/lib/misty/http/request.rb +75 -0
  13. data/lib/misty/misty.rb +51 -0
  14. data/lib/misty/openstack/aodh/aodh_v2.rb +12 -0
  15. data/lib/misty/openstack/aodh/v2.rb +20 -0
  16. data/lib/misty/openstack/ceilometer/ceilometer_v2.rb +13 -0
  17. data/lib/misty/openstack/ceilometer/v2.rb +20 -0
  18. data/lib/misty/openstack/cinder/cinder_v1.rb +35 -0
  19. data/lib/misty/openstack/cinder/cinder_v3.rb +148 -0
  20. data/lib/misty/openstack/cinder/v1.rb +24 -0
  21. data/lib/misty/openstack/cinder/v3.rb +24 -0
  22. data/lib/misty/openstack/designate/designate_v2.rb +69 -0
  23. data/lib/misty/openstack/designate/v2.rb +20 -0
  24. data/lib/misty/openstack/glance/glance_v1.rb +16 -0
  25. data/lib/misty/openstack/glance/glance_v2.rb +29 -0
  26. data/lib/misty/openstack/glance/v1.rb +20 -0
  27. data/lib/misty/openstack/glance/v2.rb +20 -0
  28. data/lib/misty/openstack/heat/heat_v1.rb +85 -0
  29. data/lib/misty/openstack/heat/v1.rb +24 -0
  30. data/lib/misty/openstack/ironic/ironic_v1.rb +71 -0
  31. data/lib/misty/openstack/ironic/v1.rb +26 -0
  32. data/lib/misty/openstack/karbor/karbor_v1.rb +32 -0
  33. data/lib/misty/openstack/karbor/v1.rb +20 -0
  34. data/lib/misty/openstack/keystone/keystone_v2_0.rb +11 -0
  35. data/lib/misty/openstack/keystone/keystone_v2_0_ext.rb +32 -0
  36. data/lib/misty/openstack/keystone/keystone_v3.rb +147 -0
  37. data/lib/misty/openstack/keystone/keystone_v3_ext.rb +124 -0
  38. data/lib/misty/openstack/keystone/v2_0.rb +23 -0
  39. data/lib/misty/openstack/keystone/v3.rb +23 -0
  40. data/lib/misty/openstack/magnum/magnum_v1.rb +41 -0
  41. data/lib/misty/openstack/magnum/v1.rb +26 -0
  42. data/lib/misty/openstack/manila/manila_v2.rb +143 -0
  43. data/lib/misty/openstack/manila/v2.rb +26 -0
  44. data/lib/misty/openstack/microversion.rb +62 -0
  45. data/lib/misty/openstack/neutron/neutron_v2_0.rb +205 -0
  46. data/lib/misty/openstack/neutron/v2_0.rb +20 -0
  47. data/lib/misty/openstack/nova/nova_v2_1.rb +269 -0
  48. data/lib/misty/openstack/nova/v2_1.rb +40 -0
  49. data/lib/misty/openstack/sahara/sahara_v1_1.rb +77 -0
  50. data/lib/misty/openstack/sahara/v1_1.rb +20 -0
  51. data/lib/misty/openstack/searchlight/searchlight_v1.rb +15 -0
  52. data/lib/misty/openstack/searchlight/v1.rb +20 -0
  53. data/lib/misty/openstack/senlin/senlin_v1.rb +66 -0
  54. data/lib/misty/openstack/senlin/v1.rb +20 -0
  55. data/lib/misty/openstack/swift/swift_v1.rb +23 -0
  56. data/lib/misty/openstack/swift/v1.rb +20 -0
  57. data/lib/misty/openstack/trove/trove_v1_0.rb +51 -0
  58. data/lib/misty/openstack/trove/v1_0.rb +20 -0
  59. data/lib/misty/openstack/zaqar/v2.rb +20 -0
  60. data/lib/misty/openstack/zaqar/zaqar_v2.rb +46 -0
  61. data/lib/misty/version.rb +2 -2
  62. data/test/integration/compute_test.rb +35 -0
  63. data/test/integration/network_test.rb +34 -0
  64. data/test/integration/orchestration_test.rb +92 -0
  65. data/test/integration/test_helper.rb +19 -0
  66. data/test/integration/vcr/compute_using_nova_v2_1.yml +1107 -0
  67. data/test/integration/vcr/network_using_neutron_v2_0.yml +1029 -0
  68. data/test/integration/vcr/orchestration_using_heat_v1.yml +1457 -0
  69. data/test/unit/auth_helper.rb +52 -0
  70. data/test/unit/auth_test.rb +99 -0
  71. data/test/unit/cloud/requests_test.rb +113 -0
  72. data/test/unit/cloud/services_test.rb +171 -0
  73. data/test/unit/cloud_test.rb +145 -0
  74. data/test/unit/http/client_test.rb +74 -0
  75. data/test/unit/http/direct_test.rb +103 -0
  76. data/test/unit/http/method_builder_test.rb +133 -0
  77. data/test/unit/http/request_test.rb +123 -0
  78. data/test/unit/misty_test.rb +36 -0
  79. data/test/unit/openstack/APIs_test.rb +40 -0
  80. data/test/unit/openstack/microversion_test.rb +70 -0
  81. data/test/unit/service_helper.rb +25 -0
  82. data/test/unit/test_helper.rb +8 -0
  83. metadata +170 -5
@@ -0,0 +1,133 @@
1
+ require 'misty/auth/auth_v2'
2
+ require 'misty/auth/auth_v3'
3
+
4
+ module Misty
5
+ class Cloud
6
+ Setup = Struct.new(:auth, :content_type, :log)
7
+
8
+ Options = Struct.new(:alarming, :baremetal, :block_storage, :clustering, :compute, :container, :data_processing,
9
+ :database, :data_protection, :dns, :identity, :image, :messaging, :metering, :network, :object_storage,
10
+ :orchestration, :search, :shared_file_systems)
11
+
12
+ attr_reader :services
13
+
14
+ def initialize(options = {:auth => {}})
15
+ @cloud = Setup.new
16
+ @cloud.auth = Misty::Auth.factory(options[:auth])
17
+ @cloud.content_type = options[:content_type] ? options[:content_type] : nil
18
+ @cloud.log = Logger.new(options[:log_file] ? options[:log_file] : Misty::LOG_FILE)
19
+ @cloud.log.level = options[:log_level] ? options[:log_level] : Misty::LOG_LEVEL
20
+
21
+ @options = Options.new
22
+ Misty.services.each do |service|
23
+ @options.send("#{service.name}=".to_sym, options[service.name] ? options[service.name] : {})
24
+ end
25
+
26
+ @services = setup_services(@options)
27
+ end
28
+
29
+ def setup_services(options)
30
+ services = {}
31
+ Misty.services.each do |service|
32
+ if options[service.name] && options[service.name][:api_version] \
33
+ && service.versions.include?(options[service.name][:api_version])
34
+ services.merge!(service.name => {service.project => options[service.name][:api_version]})
35
+ else
36
+ # Highest version is used by default!
37
+ services.merge!(service.name => {service.project => service.versions.sort[-1]})
38
+ end
39
+ end
40
+ services
41
+ end
42
+
43
+ def build_service(service_name)
44
+ project = @services[service_name].keys[0]
45
+ version = @services[service_name].fetch(project)
46
+ version = self.class.dot_to_underscore(version)
47
+ klass = Object.const_get("Misty::Openstack::#{project.capitalize}::#{version.capitalize}")
48
+ klass.new(@cloud, @options[service_name])
49
+ end
50
+
51
+ def self.dot_to_underscore(val)
52
+ val.gsub(/\./,'_')
53
+ end
54
+
55
+ def alarming
56
+ @alarming ||= build_service(:alarming)
57
+ end
58
+
59
+ def baremetal
60
+ @baremetal ||= build_service(:baremetal)
61
+ end
62
+
63
+ def block_storage
64
+ @block_storage ||= build_service(:block_storage)
65
+ end
66
+
67
+ alias volume block_storage
68
+
69
+ def clustering
70
+ @clustering ||= build_service(:clustering)
71
+ end
72
+
73
+ def compute
74
+ @compute ||= build_service(:compute)
75
+ end
76
+
77
+ def container
78
+ @container ||= build_service(:container)
79
+ end
80
+
81
+ def data_processing
82
+ @data_processing ||= build_service(:data_processing)
83
+ end
84
+
85
+ def data_protection
86
+ @data_protection ||= build_service(:data_protection)
87
+ end
88
+
89
+ def database
90
+ @database ||= build_service(:database)
91
+ end
92
+
93
+ def dns
94
+ @dns ||= build_service(:dns)
95
+ end
96
+
97
+ def identity
98
+ @identity ||= build_service(:identity)
99
+ end
100
+
101
+ def image
102
+ @image ||= build_service(:image)
103
+ end
104
+
105
+ def messaging
106
+ @messaging ||= build_service(:messaging)
107
+ end
108
+
109
+ def metering
110
+ @metering ||= build_service(:metering)
111
+ end
112
+
113
+ def network
114
+ @network ||= build_service(:network)
115
+ end
116
+
117
+ def object_storage
118
+ @object_storage ||= build_service(:object_storage)
119
+ end
120
+
121
+ def orchestration
122
+ @orchestration ||= build_service(:orchestration)
123
+ end
124
+
125
+ def search
126
+ @search ||= build_service(:search)
127
+ end
128
+
129
+ def shared_file_systems
130
+ @shared_file_systems ||= build_service(:shared_file_systems)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,115 @@
1
+ require 'misty/http/method_builder'
2
+ require 'misty/http/request'
3
+ require 'misty/http/direct'
4
+
5
+ module Misty
6
+ module HTTP
7
+ class Client
8
+ class InvalidDataError < StandardError; end
9
+
10
+ include Misty::HTTP::MethodBuilder
11
+ include Misty::HTTP::Request
12
+ include Misty::HTTP::Direct
13
+
14
+ INTERFACES = %w{admin public internal}
15
+
16
+ attr_reader :microversion
17
+
18
+ Options = Struct.new(:base_path, :base_url, :interface, :region_id, :service_names, :ssl_verify_mode, :version)
19
+
20
+ def requests
21
+ list = []
22
+ self.class.api.each do |_path, verbs|
23
+ verbs.each do |_verb, requests|
24
+ list << requests
25
+ end
26
+ end
27
+ list.flatten.sort
28
+ end
29
+
30
+ # Options - Values shown are the default
31
+ # Base path can be forced (Not recommended, mainly used for test purposes)
32
+ # :base_path => nil
33
+ # URL can be forced (Helps when setup is broken)
34
+ # :base_url => nil
35
+ # Endpoint type (admin, public or internal)
36
+ # :interface => "public"
37
+ # Region ID
38
+ # :region_id => "regionOne"
39
+ # Service name
40
+ # The Service names are pre defined but more can be added using this option.
41
+ # :service_name
42
+ # SSL Verify Mode
43
+ # :ssl_verify_mode => true
44
+ # (micro)version: Can be numbered (3.1) or by state (CURRENT, LATEST or SUPPORTED)
45
+ # :version => "CURRENT"
46
+ def initialize(cloud, options)
47
+ @cloud = cloud
48
+ @options = setup(options)
49
+ @uri = URI.parse(@cloud.auth.get_endpoint(@options.service_names, @options.region_id, @options.interface))
50
+ @base_path = @options.base_path ? @options.base_path : @uri.path
51
+ @base_path = @base_path.chomp("/")
52
+ @http = net_http(@uri)
53
+ @version = nil
54
+ @microversion = false
55
+ end
56
+
57
+ # Sub classes to override
58
+ # When a catalog provides a base path and the Service API definition containts the generic equivalent as prefix
59
+ # then the preifx is redundant and must be removed from the path.
60
+ # For example:
61
+ # Catalog provides "http://192.0.2.21:8004/v1/48985e6b8da145699d411f12a3459fca"
62
+ # and Service API has "/v1/{tenant_id}/stacks"
63
+ # then the path prefix is ignored and path is only "/stacks"
64
+ def self.prefix_path_to_ignore
65
+ ""
66
+ end
67
+
68
+ def headers_default
69
+ Misty::HEADER_JSON
70
+ end
71
+
72
+ def headers
73
+ h = headers_default.merge("X-Auth-Token" => "#{@cloud.auth.get_token}")
74
+ h.merge!(microversion_header) if microversion
75
+ h
76
+ end
77
+
78
+ private
79
+
80
+ def baseclass
81
+ self.class.to_s.split('::')[-1]
82
+ end
83
+
84
+ def net_http(uri)
85
+ http = Net::HTTP.new(uri.host, uri.port)
86
+ http.set_debug_output($stdout) if @cloud.log.level == Logger::DEBUG
87
+ if uri.scheme == "https"
88
+ http.use_ssl = true
89
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @options[:ssl_verify_mode]
90
+ end
91
+ http
92
+ end
93
+
94
+ def setup(params)
95
+ options = Options.new()
96
+ options.base_path = params[:base_path] ? params[:base_path] : nil
97
+ options.base_url = params[:base_url] ? params[:base_url] : nil
98
+ options.interface = params[:interface] ? params[:interface] : "public"
99
+ options.region_id = params[:region_id] ? params[:region_id] : "regionOne"
100
+ options.service_names = params[:service_name] ? self.class.service_names << params[:service_name] : self.class.service_names
101
+ options.ssl_verify_mode = params[:ssl_verify_mode] ? params[:ssl_verify_mode] : true
102
+ options.version = params[:version] ? params[:version] : "CURRENT"
103
+
104
+ unless INTERFACES.include?(options.interface)
105
+ raise InvalidDataError, "Options ':interface'must be one of #{INTERFACES}"
106
+ end
107
+
108
+ unless options.ssl_verify_mode == !!options.ssl_verify_mode
109
+ raise InvalidDataError, "Options ':ssl_verify_mode' must be a boolean"
110
+ end
111
+ options
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,26 @@
1
+ module Misty
2
+ module HTTP
3
+ # Provides methods to submit the current service with a path and override @base_path if needed
4
+ module Direct
5
+ def base_set(base_path)
6
+ base = base_path ? base_path : @base_path
7
+ end
8
+
9
+ def delete(path, base_path = nil)
10
+ http_delete(base_set(base_path) + path, headers)
11
+ end
12
+
13
+ def get(path, base_path = nil)
14
+ http_get(base_set(base_path) + path, headers)
15
+ end
16
+
17
+ def post(path, data, base_path = nil)
18
+ http_post(base_set(base_path) + path, headers, data)
19
+ end
20
+
21
+ def put(path, data, base_path = nil)
22
+ http_put(base_set(base_path) + path, headers, data)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,96 @@
1
+ module Misty
2
+ module HTTP
3
+ module MethodBuilder
4
+ def method_missing(method_name, *args)
5
+ if method = get_method(method_name)
6
+ method_call(method, *args)
7
+ else
8
+ raise NoMethodError, "API #{baseclass.downcase}: method #{method_name} not implemented!"
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def method_call(method, *args)
15
+ base, path = prefixed_path(method[:path])
16
+ size_path_parameters = count_path_params(path)
17
+ path_params = args[0, size_path_parameters]
18
+ args_left = args - path_params
19
+ path = inject_elements(path, path_params)
20
+ final_path = base_filter(base, path)
21
+
22
+ case method[:request]
23
+ when :COPY
24
+ http_copy(final_path, headers)
25
+ when :DELETE
26
+ http_delete(final_path, headers)
27
+ when :GET
28
+ final_path << query_param(args_left[0]) if args_left && args_left.size == 1
29
+ http_get(final_path, headers)
30
+ when :HEAD
31
+ http_head(final_path, headers, data)
32
+ when :POST
33
+ data = args_left[0] if args_left && args_left.size == 1
34
+ http_post(final_path, headers, data)
35
+ when :PUT
36
+ data = args_left[0] if args_left && args_left.size == 1
37
+ http_put(final_path, headers, data)
38
+ when :PATCH
39
+ data = args_left[0] if args_left && args_left.size == 1
40
+ http_patch(final_path, headers, data)
41
+ else
42
+ raise SyntaxError, "Invalid HTTP request: #{method[:request]}"
43
+ end
44
+ end
45
+
46
+ def base_filter(base, path)
47
+ return (@base_path + path) if base
48
+ return path
49
+ end
50
+
51
+ def count_path_params(str)
52
+ subpath = /(.*)(\{.*\})(.*)/.match(str)
53
+ return 0 unless subpath
54
+ return count_path_params(subpath[1]) + 1
55
+ end
56
+
57
+ def get_method(name)
58
+ self.class.api.each do |path, requests|
59
+ requests.each do |request, methods_list|
60
+ if methods_list.include?(name)
61
+ return {:path => path, :request => request, :name => name}
62
+ end
63
+ end
64
+ end
65
+ nil
66
+ end
67
+
68
+ def inject_elements(str, args)
69
+ subpath = /(.*)(\{.*\})(.*)/.match(str)
70
+ return str unless subpath
71
+ arg = args.pop
72
+ raise ArgumentError, "Not enough arguments" unless arg
73
+ return inject_elements(subpath[1], args) + arg + subpath[3]
74
+ end
75
+
76
+ def prefixed_path(path)
77
+ unless self.class.prefix_path_to_ignore.empty?
78
+ if path =~ %r{#{self.class.prefix_path_to_ignore}}
79
+ return true, path.gsub(self.class.prefix_path_to_ignore, '')
80
+ else
81
+ return false, path
82
+ end
83
+ end
84
+ return true, path
85
+ end
86
+
87
+ def query_param(data)
88
+ if data.is_a? String
89
+ str = '?'
90
+ str.force_encoding('ASCII-8BIT')
91
+ str << data unless data.empty?
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,75 @@
1
+ module Misty
2
+ module HTTP
3
+ module Request
4
+ def decode?(response)
5
+ if @cloud.content_type != :json && response.code =~ /2??/ && !response.is_a?(Net::HTTPNoContent) \
6
+ && !response.is_a?(Net::HTTPResetContent) && response.header["content-type"] \
7
+ && response.header["content-type"].include?("application/json")
8
+ true
9
+ end
10
+ end
11
+
12
+ def http(request)
13
+ response = @http.request(request)
14
+ response.body = JSON.load(response.body) if decode?(response)
15
+ response
16
+ end
17
+
18
+ def http_delete(path, headers)
19
+ @cloud.log.info(http_to_s(path, headers))
20
+ request = Net::HTTP::Delete.new(path, headers)
21
+ http(request)
22
+ end
23
+
24
+ def http_copy(path, headers)
25
+ @cloud.log.info(http_to_s(path, headers))
26
+ request = Net::HTTP::Copy.new(path, headers)
27
+ http(request)
28
+ end
29
+
30
+ def http_get(path, headers)
31
+ @cloud.log.info(http_to_s(path, headers))
32
+ request = Net::HTTP::Get.new(path, headers)
33
+ http(request)
34
+ end
35
+
36
+ def http_head(path, headers)
37
+ @cloud.log.info(http_to_s(path, headers))
38
+ request = Net::HTTP::Head.new(path, headers)
39
+ http(request)
40
+ end
41
+
42
+ def http_options(path, headers)
43
+ @cloud.log.info(http_to_s(path, headers))
44
+ request = Net::HTTP::Options.new(path, headers)
45
+ http(request)
46
+ end
47
+
48
+ def http_patch(path, headers, data)
49
+ @cloud.log.info(http_to_s(path, headers, data))
50
+ request = Net::HTTP::Patch.new(path, headers)
51
+ request.body = Misty.to_json(data)
52
+ http(request)
53
+ end
54
+
55
+ def http_post(path, headers, data)
56
+ @cloud.log.info(http_to_s(path, headers, data))
57
+ request = Net::HTTP::Post.new(path, headers)
58
+ request.body = Misty.to_json(data)
59
+ http(request)
60
+ end
61
+
62
+ def http_put(path, headers, data)
63
+ @cloud.log.info(http_to_s(path, headers, data))
64
+ request = Net::HTTP::Put.new(path, headers)
65
+ request.body = Misty.to_json(data)
66
+ http(request)
67
+ end
68
+
69
+ def http_to_s(path, headers, data = nil)
70
+ log = "base_url='#{@uri.host}:#{@uri.port}', path='#{path}', header=#{headers}"
71
+ log << ", data='#{data}'" if data
72
+ end
73
+ end
74
+ end
75
+ end