misty 1.3.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +332 -267
  3. data/lib/misty.rb +1 -1
  4. data/lib/misty/auth.rb +17 -6
  5. data/lib/misty/auth/auth_v2.rb +3 -0
  6. data/lib/misty/auth/auth_v3.rb +13 -5
  7. data/lib/misty/auth/name.rb +3 -3
  8. data/lib/misty/client_pack.rb +2 -2
  9. data/lib/misty/cloud.rb +111 -76
  10. data/lib/misty/config.rb +138 -0
  11. data/lib/misty/{auth/errors.rb → errors.rb} +9 -1
  12. data/lib/misty/http/direct.rb +18 -1
  13. data/lib/misty/http/method_builder.rb +10 -17
  14. data/lib/misty/http/net_http.rb +1 -1
  15. data/lib/misty/http/request.rb +26 -14
  16. data/lib/misty/microversion.rb +22 -41
  17. data/lib/misty/misty.rb +14 -24
  18. data/lib/misty/openstack/cinder/v3.rb +8 -0
  19. data/lib/misty/openstack/ironic/v1.rb +8 -0
  20. data/lib/misty/openstack/magnum/v1.rb +5 -1
  21. data/lib/misty/openstack/manila/v2.rb +8 -0
  22. data/lib/misty/openstack/nova/v2_1.rb +13 -8
  23. data/lib/misty/openstack/service.rb +88 -0
  24. data/lib/misty/openstack/swift/v1.rb +2 -2
  25. data/lib/misty/service.rb +9 -12
  26. data/lib/misty/version.rb +1 -1
  27. data/test/integration/{network_test.rb → networking_test.rb} +8 -8
  28. data/test/integration/test_helper.rb +1 -0
  29. data/test/integration/vcr/{network_using_neutron_v2_0.yml → networking_using_neutron_v2_0.yml} +0 -0
  30. data/test/unit/auth/name_test.rb +31 -27
  31. data/test/unit/auth_helper.rb +4 -4
  32. data/test/unit/auth_test.rb +44 -30
  33. data/test/unit/cloud/config_test.rb +165 -0
  34. data/test/unit/cloud/requests_test.rb +0 -12
  35. data/test/unit/cloud/services_test.rb +41 -12
  36. data/test/unit/cloud_test.rb +35 -44
  37. data/test/unit/http/request_test.rb +1 -1
  38. data/test/unit/microversion_test.rb +59 -35
  39. data/test/unit/misty_test.rb +1 -1
  40. data/test/unit/openstack/service_test.rb +52 -0
  41. data/test/unit/service_helper.rb +23 -20
  42. data/test/unit/services_test.rb +1 -1
  43. data/test/unit/test_helper.rb +0 -4
  44. metadata +37 -22
  45. data/lib/misty/client.rb +0 -104
  46. data/test/unit/client_test.rb +0 -97
@@ -1,4 +1,3 @@
1
- require 'logger'
2
1
  require 'json'
3
2
  require 'net/http'
4
3
  require 'time'
@@ -6,3 +5,4 @@ require 'uri'
6
5
  require 'misty/misty'
7
6
  require 'misty/autoload'
8
7
  require 'misty/cloud'
8
+ require 'misty/errors'
@@ -1,20 +1,29 @@
1
- require 'misty/auth/errors'
1
+ require 'logger'
2
2
  require 'misty/auth/name'
3
3
  require 'misty/http/net_http'
4
+ require 'misty/config'
4
5
 
5
6
  module Misty
7
+
8
+ # The credentials are a combination of "id" and "name" used to uniquely identify the context.
9
+ # +Misty::Auth+ is mixing the common interface between +Misty::AuthV3+ and +Misty::AuthV2+
10
+ #
6
11
  module Auth
7
12
  include Misty::HTTP::NetHTTP
8
13
 
9
14
  attr_reader :catalog, :token
10
15
 
11
- def self.factory(auth, config)
16
+ def self.build(auth)
12
17
  version = auth[:tenant_id] || auth[:tenant] ? 'V2' : 'V3'
13
18
  klass = Object.const_get("Misty::Auth#{version}")
14
- klass.new(auth, config)
19
+ klass.new(auth)
15
20
  end
16
21
 
17
- def initialize(auth, config)
22
+ # ==== Attributes
23
+ #
24
+ # * +auth+ - Hash of credentials parameters for authentication
25
+ #
26
+ def initialize(auth)
18
27
  if auth[:context]
19
28
  # bypass the authentication by given token catalog and expire date
20
29
  @token = auth[:context][:token]
@@ -23,15 +32,17 @@ module Misty
23
32
  else
24
33
  raise URLError, 'No URL provided' if auth[:url].nil? || auth[:url].empty?
25
34
  @uri = URI.parse(auth[:url])
26
- @config = config
35
+ @ssl_verify_mode = auth[:ssl_verify_mode] ? auth[:ssl_verify_mode] : Misty::Config::SSL_VERIFY_MODE
27
36
  @credentials = set_credentials(auth)
28
37
  @token, @catalog, @expires = set(authenticate)
38
+ # TODO: Pass main log object
39
+ @log = Logger.new('/dev/null')
29
40
  end
30
41
  end
31
42
 
32
43
  def authenticate
33
44
  Misty::HTTP::NetHTTP.http_request(
34
- @uri, ssl_verify_mode: @config.ssl_verify_mode, log: @config.log
45
+ @uri, ssl_verify_mode: @ssl_verify_mode, log: @log
35
46
  ) do |connection|
36
47
  response = connection.post(path, @credentials.to_json,
37
48
  { 'Content-Type' => 'application/json', 'Accept' => 'application/json' })
@@ -1,6 +1,9 @@
1
1
  require 'misty/auth'
2
2
 
3
3
  module Misty
4
+ #
5
+ # Openstack Identity service Keystone version 2.0, which is deprecated, uses a tenant name or id to authenticates
6
+ # +Misty::AuthV2+ is used if the authentication credentials contains a tenant name or id.
4
7
  class AuthV2
5
8
  include Misty::Auth
6
9
 
@@ -48,10 +48,11 @@ module Misty
48
48
 
49
49
  def set_credentials(auth)
50
50
  if auth[:project_id] || auth[:project]
51
- # scope: project
52
- project_domain_id = auth[:project_domain_id]
53
- if project_domain_id.nil? && auth[:project_domain].nil?
51
+ if auth[:project_domain_id].nil? && auth[:project_domain].nil?
54
52
  project_domain_id = Misty::DOMAIN_ID
53
+ else
54
+ project_domain_id = auth[:project_domain_id] if auth[:project_domain_id]
55
+ project_domain = auth[:project_domain] if auth[:project_domain]
55
56
  end
56
57
 
57
58
  @project = Misty::Auth::ProjectScope.new(auth[:project_id], auth[:project])
@@ -69,10 +70,17 @@ module Misty
69
70
  if auth[:token]
70
71
  @token = auth[:token]
71
72
  else
72
- user_domain_id = auth[:user_domain_id] ? auth[:user_domain_id] : Misty::DOMAIN_ID
73
73
  @user = Misty::Auth::User.new(auth[:user_id], auth[:user])
74
+
75
+ if auth[:user_domain_id].nil? && auth[:user_domain].nil?
76
+ user_domain_id = Misty::DOMAIN_ID
77
+ else
78
+ user_domain_id = auth[:user_domain_id] if auth[:user_domain_id]
79
+ user_domain = auth[:user_domain] if auth[:user_domain]
80
+ end
81
+
82
+ @user.domain = Misty::Auth::Name.new(user_domain_id, user_domain)
74
83
  @user.password = auth[:password]
75
- @user.domain = Misty::Auth::Name.new(user_domain_id, auth[:user_domain])
76
84
  end
77
85
 
78
86
  credentials
@@ -11,7 +11,7 @@ module Misty
11
11
  data.merge!(to_h(:name))
12
12
  data.merge!({ :domain => @domain.identity })
13
13
  else
14
- raise CredentialsError, "#{self.class}: An Id, or a name with its domain, must be provided"
14
+ raise Misty::Config::CredentialsError, "#{self.class}: An Id, or a name with its domain, must be provided"
15
15
  end
16
16
  data
17
17
  end
@@ -21,7 +21,7 @@ module Misty
21
21
  def identity
22
22
  return to_h(:id) unless id.nil?
23
23
  return to_h(:name) unless name.nil?
24
- raise CredentialsError, "#{self.class}: No available id or name"
24
+ raise Misty::Config::CredentialsError, "#{self.class}: No available id or name"
25
25
  end
26
26
 
27
27
  def to_h(var)
@@ -50,7 +50,7 @@ module Misty
50
50
 
51
51
  def identity
52
52
  data = super
53
- raise CredentialsError, "#{self.class}: No password available" if password.nil?
53
+ raise Misty::Config::CredentialsError, "#{self.class}: No password available" if password.nil?
54
54
  data.merge!(to_h(:password))
55
55
  { :user => data }
56
56
  end
@@ -1,4 +1,4 @@
1
- require 'misty/client'
1
+ require 'misty/openstack/service'
2
2
  require 'misty/http/net_http'
3
3
  require 'misty/http/method_builder'
4
4
  require 'misty/http/request'
@@ -7,7 +7,7 @@ require 'misty/http/header'
7
7
 
8
8
  module Misty
9
9
  module ClientPack
10
- include Misty::Client
10
+ include Misty::Openstack::Service
11
11
  include Misty::HTTP::NetHTTP
12
12
  include Misty::HTTP::MethodBuilder
13
13
  include Misty::HTTP::Request
@@ -1,150 +1,185 @@
1
+ require 'misty/config'
1
2
  require 'misty/auth/auth_v2'
2
3
  require 'misty/auth/auth_v3'
3
4
  require 'misty/http/header'
4
5
 
5
6
  module Misty
6
- class Cloud
7
- class Config
8
- attr_accessor :auth, :content_type, :interface, :log, :region_id, :ssl_verify_mode, :headers
9
- end
10
-
11
- attr_reader :auth
12
7
 
8
+ # +Misty::Cloud+ is the main OpenStack cloud class.
9
+ # An instance holds authentication information such as token, catalog and contains all available services as methods.
10
+ #
11
+ class Cloud
13
12
  def self.dot_to_underscore(val)
14
13
  val.gsub(/\./,'_')
15
14
  end
16
15
 
17
- def initialize(params)
18
- @params = params
19
- @config = self.class.set_configuration(params)
20
- @auth = Misty::Auth.factory(params[:auth], @config)
21
- end
22
-
23
- def self.set_configuration(params)
24
- config = Config.new
25
- config.content_type = params[:content_type] ? params[:content_type] : Misty::CONTENT_TYPE
26
- config.interface = params[:interface] ? params[:interface] : Misty::INTERFACE
27
- config.log = Logger.new(params[:log_file] ? params[:log_file] : Misty::LOG_FILE)
28
- config.log.level = params[:log_level] ? params[:log_level] : Misty::LOG_LEVEL
29
- config.region_id = params[:region_id] ? params[:region_id] : Misty::REGION_ID
30
- config.ssl_verify_mode = params.key?(:ssl_verify_mode) ? params[:ssl_verify_mode] : Misty::SSL_VERIFY_MODE
31
- config.headers = Misty::HTTP::Header.new('Accept' => 'application/json; q=1.0')
32
- config.headers.add(params[:headers]) if params[:headers] && !params[:headers].empty?
33
- config
16
+ # ==== Attributes
17
+ #
18
+ # * +arg+ - Hash of configuration parameters for authentication, log, and services options.
19
+ #
20
+ def initialize(arg)
21
+ @config = Misty::Config.new(arg)
34
22
  end
35
23
 
36
- def build_service(service_name)
37
- service = Misty.services.find {|service| service.name == service_name}
38
- options = @params[service.name] ? @params[service.name] : {}
39
- version = self.class.dot_to_underscore(service.version(options[:api_version]))
24
+ def build_service(method)
25
+ service = Misty.services.find {|service| service.name == method}
26
+ service_config = @config.get_service(method)
27
+ version = self.class.dot_to_underscore(service.default_version(service_config[:config][:api_version]))
40
28
  klass = Object.const_get("Misty::Openstack::#{service.project.capitalize}::#{version.capitalize}")
41
- klass.new(@auth, @config, options)
29
+ klass.new(service_config)
42
30
  end
43
31
 
44
- def application_catalog
45
- @application_catalog ||= build_service(:application_catalog)
32
+ def application_catalog(arg = {})
33
+ @application_catalog ||= build_service(__method__)
34
+ @application_catalog.request_config(arg)
35
+ @application_catalog
46
36
  end
47
37
 
48
- def alarming
49
- @alarming ||= build_service(:alarming)
38
+ def alarming(arg = {})
39
+ @alarming ||= build_service(__method__)
40
+ @alarming.request_config(arg)
41
+ @alarming
50
42
  end
51
43
 
52
- def backup
53
- @backup ||= build_service(:backup)
44
+ def backup(arg = {})
45
+ @backup ||= build_service(__method__)
46
+ @backup.request_config(arg)
47
+ @backup
54
48
  end
55
49
 
56
- def baremetal
57
- @baremetal ||= build_service(:baremetal)
50
+ def baremetal(arg = {})
51
+ @baremetal ||= build_service(__method__)
52
+ @baremetal.request_config(arg)
53
+ @baremetal
58
54
  end
59
55
 
60
- def block_storage
61
- @block_storage ||= build_service(:block_storage)
56
+ def block_storage(arg = {})
57
+ @block_storage ||= build_service(__method__)
58
+ @block_storage.request_config(arg)
59
+ @block_storage
62
60
  end
63
61
 
64
- def clustering
65
- @clustering ||= build_service(:clustering)
62
+ def clustering(arg = {})
63
+ @clustering ||= build_service(__method__)
64
+ @clustering.request_config(arg)
65
+ @clustering
66
66
  end
67
67
 
68
- def compute
69
- @compute ||= build_service(:compute)
68
+ def compute(arg = {})
69
+ @compute ||= build_service(__method__)
70
+ @compute.request_config(arg)
71
+ @compute
70
72
  end
71
73
 
72
- def container_infrastructure_management
73
- @container_infrastructure_management ||= build_service(:container_infrastructure_management)
74
+ def container_infrastructure_management(arg = {})
75
+ @container_infrastructure_management ||= build_service(__method__)
76
+ @container_infrastructure_management.request_config(arg)
77
+ @container_infrastructure_management
74
78
  end
75
79
 
76
- def data_processing
77
- @data_processing ||= build_service(:data_processing)
80
+ def data_processing(arg = {})
81
+ @data_processing ||= build_service(__method__)
82
+ @data_processing.request_config(arg)
83
+ @data_processing
78
84
  end
79
85
 
80
- def data_protection_orchestration
81
- @data_protection_orchestration ||= build_service(:data_protection_orchestration)
86
+ def data_protection_orchestration(arg = {})
87
+ @data_protection_orchestration ||= build_service(__method__)
88
+ @data_protection_orchestration.request_config(arg)
89
+ @data_protection_orchestration
82
90
  end
83
91
 
84
- def database
85
- @database ||= build_service(:database)
92
+ def database(arg = {})
93
+ @database ||= build_service(__method__)
94
+ @database.request_config(arg)
95
+ @database
86
96
  end
87
97
 
88
- def domain_name_server
89
- @domain_name_server ||= build_service(:domain_name_server)
98
+ def dns(arg = {})
99
+ @dns ||= build_service(__method__)
100
+ @dns.request_config(arg)
101
+ @dns
90
102
  end
91
103
 
92
- def identity
93
- @identity ||= build_service(:identity)
104
+ def identity(arg = {})
105
+ @identity ||= build_service(__method__)
106
+ @identity.request_config(arg)
107
+ @identity
94
108
  end
95
109
 
96
- def image
97
- @image ||= build_service(:image)
110
+ def image(arg = {})
111
+ @image ||= build_service(__method__)
112
+ @image.request_config(arg)
113
+ @image
98
114
  end
99
115
 
100
- def load_balancer
101
- @load_balancer ||= build_service(:load_balancer)
116
+ def load_balancer(arg = {})
117
+ @load_balancer ||= build_service(__method__)
118
+ @load_balancer.request_config(arg)
119
+ @load_balancer
102
120
  end
103
121
 
104
- def messaging
105
- @messaging ||= build_service(:messaging)
122
+ def messaging(arg = {})
123
+ @messaging ||= build_service(__method__)
124
+ @messaging.request_config(arg)
125
+ @messaging
106
126
  end
107
127
 
108
- def metering
109
- @metering ||= build_service(:metering)
128
+ def metering(arg = {})
129
+ @metering ||= build_service(__method__)
130
+ @metering.request_config(arg)
131
+ @metering
110
132
  end
111
133
 
112
- def networking
113
- @networking ||= build_service(:networking)
134
+ def networking(arg = {})
135
+ @networking ||= build_service(__method__)
136
+ @networking.request_config(arg)
137
+ @networking
114
138
  end
115
139
 
116
- def nfv_orchestration
117
- @nfv_orchestration ||= build_service(:nfv_orchestration)
140
+ def nfv_orchestration(arg = {})
141
+ @nfv_orchestration ||= build_service(__method__)
142
+ @nfv_orchestration.request_config(arg)
143
+ @nfv_orchestration
118
144
  end
119
145
 
120
- def object_storage
121
- @object_storage ||= build_service(:object_storage)
146
+ def object_storage(arg = {})
147
+ @object_storage ||= build_service(__method__)
148
+ @object_storage.request_config(arg)
149
+ @object_storage
122
150
  end
123
151
 
124
- def orchestration
125
- @orchestration ||= build_service(:orchestration)
152
+ def orchestration(arg = {})
153
+ @orchestration ||= build_service(__method__)
154
+ @orchestration.request_config(arg)
155
+ @orchestration
126
156
  end
127
157
 
128
- def search
129
- @search ||= build_service(:search)
158
+ def search(arg = {})
159
+ @search ||= build_service(__method__)
160
+ @search.request_config(arg)
161
+ @search
130
162
  end
131
163
 
132
- def shared_file_systems
133
- @shared_file_systems ||= build_service(:shared_file_systems)
164
+ def shared_file_systems(arg = {})
165
+ @shared_file_systems ||= build_service(__method__)
166
+ @shared_file_systems.request_config(arg)
167
+ @shared_file_systems
134
168
  end
135
169
 
136
- alias dns domain_name_server
170
+ alias domain_name_server dns
137
171
  alias volume block_storage
138
172
 
139
173
  private
140
174
 
141
- def method_missing(method_name)
175
+ def method_missing(method_name) # TODO, *args)
142
176
  services_avail = []
143
177
  Misty.services.names.each do |service_name|
144
178
  services_avail << service_name if /#{method_name}/.match(service_name)
145
179
  end
146
180
 
147
181
  if services_avail.size == 1
182
+ # TODO: Add args
148
183
  self.send(services_avail[0])
149
184
  return self.instance_variable_get("@#{services_avail[0]}")
150
185
  elsif services_avail.size > 1
@@ -0,0 +1,138 @@
1
+ require 'logger'
2
+ require 'misty/auth/auth_v2'
3
+ require 'misty/auth/auth_v3'
4
+ require 'misty/http/header'
5
+
6
+ module Misty
7
+ class Config
8
+ # Default REST content type. Use :json or :hash
9
+ CONTENT_TYPE = :hash
10
+
11
+ # Valid content format
12
+ CONTENT_TYPES = %i{hash json}
13
+
14
+ # Default Interface
15
+ INTERFACE = 'public'
16
+
17
+ # Valid endpoint interfaces
18
+ INTERFACES = %w{admin public internal}
19
+
20
+ # Default Log file
21
+ LOG_FILE = '/dev/null'
22
+
23
+ # Default Log level
24
+ LOG_LEVEL = Logger::INFO
25
+
26
+ # Default Region
27
+ REGION_ID = 'regionOne'
28
+
29
+ # Default when uri.scheme is https
30
+ SSL_VERIFY_MODE = true
31
+
32
+ # ==== Attributes
33
+ #
34
+ # * +arg+ - +Hash+ of configuration options
35
+
36
+ attr_reader :auth, :log, :services
37
+
38
+ def initialize(arg)
39
+ raise CredentialsError if arg.nil? || arg.empty? || arg[:auth].nil? || arg[:auth].empty?
40
+ @auth = Misty::Auth.build(arg[:auth]) # TODO: pass @log
41
+ @log = set_log(arg[:log_file], arg[:log_level])
42
+ @globals = set_config(arg)
43
+ @services = {}
44
+ # TODO: Adjust Services to use enumerable
45
+ arg.each do |e, k|
46
+ Misty::SERVICES.each do |serv|
47
+ @services[e] = k if serv[:name] == e
48
+ end
49
+ end
50
+ end
51
+
52
+ def get_service(method)
53
+ set = {}
54
+ set[:auth] = @auth
55
+ set[:log] = @log
56
+ service_config = @services.key?(method) ? @services[method] : {}
57
+ if service_config
58
+ set[:config] = set_config(service_config, @globals)
59
+ set[:config].merge!(set_service(service_config))
60
+ else
61
+ set[:config] = @globals
62
+ end
63
+ set
64
+ end
65
+
66
+ def set_service(arg)
67
+ set = {}
68
+ set[:base_path] = arg[:base_path] ? arg[:base_path] : nil
69
+ set[:base_url] = arg[:base_url] ? arg[:base_url] : nil
70
+ set[:version] = arg[:version] ? arg[:version] : nil
71
+ set[:api_version] = arg[:api_version] ? arg[:api_version] : nil
72
+ set
73
+ end
74
+
75
+ private
76
+
77
+ def get_defaults
78
+ set = {}
79
+ set[:content_type] = CONTENT_TYPE
80
+ set[:headers] = HTTP::Header.new('Accept' => 'application/json; q=1.0')
81
+ set[:interface] = INTERFACE
82
+ set[:region_id] = REGION_ID
83
+ set[:ssl_verify_mode] = SSL_VERIFY_MODE
84
+ set
85
+ end
86
+
87
+ def set_config(arg = {}, defaults = get_defaults)
88
+ set = {}
89
+ set[:content_type] = set_content_type(arg[:content_type], defaults[:content_type])
90
+ set[:headers] = set_headers(arg[:headers], defaults[:headers])
91
+ set[:interface] = set_interface(arg[:interface], defaults[:interface])
92
+ set[:region_id] = set_region_id(arg[:region_id], defaults[:region_id])
93
+ set[:ssl_verify_mode] = set_ssl_verify_mode(arg[:ssl_verify_mode], defaults[:ssl_verify_mode])
94
+ set
95
+ end
96
+
97
+
98
+ def set_content_type(val, default)
99
+ res = val.nil? ? default : val
100
+ raise InvalidDataError, "Config ':content_type' must be one of #{CONTENT_TYPES}" unless CONTENT_TYPES.include?(res)
101
+ res
102
+ end
103
+
104
+ def set_headers(val, default)
105
+ res = if val && !val.empty?
106
+ default.add(val)
107
+ default
108
+ else default
109
+ default
110
+ end
111
+ res
112
+ end
113
+
114
+ def set_interface(val, default)
115
+ res = val.nil? ? default : val
116
+ raise InvalidDataError, "Config ':interface' must be one of #{INTERFACES}" unless INTERFACES.include?(res)
117
+ res
118
+ end
119
+
120
+ def set_log(file, level)
121
+ log = Logger.new(file ? file : LOG_FILE)
122
+ log.level = level ? level : LOG_LEVEL
123
+ log
124
+ end
125
+
126
+ def set_region_id(val, default)
127
+ res = val.nil? ? default : val
128
+ raise InvalidDataError, "Config ':region_id' must be a String" unless res.kind_of? String
129
+ res
130
+ end
131
+
132
+ def set_ssl_verify_mode(val, default)
133
+ res = val.nil? ? default : val
134
+ raise InvalidDataError, "Config ':ssl_verify_mode' must be a Boolean" unless res == !!res
135
+ res
136
+ end
137
+ end
138
+ end