misty 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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