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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bdf55abfb46eb97427695bde44d502dbfe21e26b
4
- data.tar.gz: b6359b19815a9e30c6c1f5cce091fee7d956e73e
3
+ metadata.gz: ad9014122c54bf48aae6d26bfc8056ba3eca7e40
4
+ data.tar.gz: f0e8ad48f12c7011f84f83b460bef8651fdc0eb7
5
5
  SHA512:
6
- metadata.gz: 77b92ee3e216632d8091f5dfd65bae72e794979d22a5efc3b255b7b214aefa1ebb90f4ed8ecaf1d03c9a8060b1b9734d49a828e587f7ea25e6bc45398b6dd676
7
- data.tar.gz: c94d2805b9a0faa2e193d691effb9cc24a3d5b4b876bc8683084e95f3e83bf89e5250c4b7ede4617d8ce5c98fe3af7372ffa4603ea579c77ffe0779c48e6ef8f
6
+ metadata.gz: 9f59bba8de14cd8657b22db9eff2b63e9ee08abe4f65fba9b8e15015547a5e4d3d3032143389b44cd6c029eb3911a2a9b66056f77445b1860674456d2a72c9c2
7
+ data.tar.gz: 024efa940ad8cb5ec44ed28effd7fc7a399225a3a7fa592341def537d64492ac24911d480bc3e3351bd389a4ac19a75ae8a71b898e6a00edfdb24a5d9b04849d
data/README.md CHANGED
@@ -1 +1,222 @@
1
- # Misty is coming soon!
1
+ # Introduction
2
+ Misty is a HTTP client for OpenStack APIs, aiming to be fast and to provide a flexible and at same time exhaustive
3
+ APIs experience.
4
+
5
+ ## Features
6
+ * Exhaustive and latest Openstack APIs
7
+ * Multiple Service versions
8
+ * Microversions
9
+ * Based upon Net/HTTP
10
+ * Minimalistic gem dependencies - Only json is required
11
+ * Dynamic services by autoloading only required service's version
12
+ * Token automatically refreshed when expired
13
+ * Raw JSON or Ruby format for queries and responses
14
+ * Persistent HTTP connections (default since HTTP 1.1 anyway) but for the authentication bootstrapping
15
+ * Direct HTTP Methods for custom needs
16
+
17
+ ## A solid KISS
18
+ For REST transactions, Misty uses only Net/HTTP from the Ruby Standard Library. So besides 'json', no other gem are
19
+ required.
20
+ Because OpenStack authentication and Service Catalog management are very specific and shared by all the APIs, once taken
21
+ care of, there is no need for a complex HTTP framework.
22
+ This offers a solid foundation with reduced dependencies.
23
+
24
+ ## APIs Definitions
25
+ The rich variety of OpenStack projects requires lots of Application Program Interfaces to handle.
26
+ Maintaining and extending those APIs is a structural complexity challenge.
27
+ Therefore the more automated the process, the better.
28
+ Thanks to the help of Phoenix project, the OpenStack API-ref [1] provides standardization of the OpenStack APIs.
29
+ The APIs can be processed almost automatically from the API-ref reference manuals (misty-builder).
30
+ This allows:
31
+ * More consistent APIs using automated control
32
+ * More recent APIs definitions
33
+ * Easier to add APIs
34
+
35
+ [1] https://developer.openstack.org/api-guide/quick-start/
36
+
37
+ # Install & Use
38
+
39
+ ## Fetch and install
40
+ ``` ruby
41
+ gem install misty
42
+ ```
43
+
44
+ ## Quick start
45
+ ```ruby
46
+ require 'misty'
47
+
48
+ auth_v3 = {
49
+ :url => "http://localhost:5000",
50
+ :user => "admin",
51
+ :password => "secret",
52
+ :project => "admin",
53
+ :domain => "default"
54
+ }
55
+
56
+ openstack = Misty::Cloud.new(:auth => auth_v3)
57
+
58
+ puts openstack.compute.list_servers.body
59
+ puts openstack.compute.list_flavors.body
60
+ networks = openstack.network.list_networks
61
+ network_id = networks.body["networks"][0]['id']
62
+ network = openstack.network.show_network_details(network_id)
63
+ puts network.body
64
+ ```
65
+
66
+ ## Services
67
+ Once the Misty::Cloud object is created, the Openstack services can be used.
68
+
69
+ The Cloud object authenticates against the identity server (bootstrap process) and obtains the service catalog.
70
+ When an OpenStack API service is used, its endpoint is determined from the catalog and the service is dynamically called
71
+ by Misty so only the services used are loaded.
72
+
73
+ The service generic name, such as `compute`, is used to submit requests with an OpenStack service API.
74
+
75
+ ```ruby
76
+ openstack = Misty::Cloud.new(:auth => { ... })
77
+ openstack.compute.list_servers
78
+ openstack.network.list_networks
79
+ openstack.network.create_network("network": {"name": "my-network"})
80
+ ```
81
+
82
+ To obtain the list of supported services:
83
+ ```ruby
84
+ require 'misty'
85
+ puts Misty.services
86
+ ```
87
+
88
+ Which produces the equivalent of the following:
89
+
90
+ name | project | versions
91
+ --- | --- | ---
92
+ alarming | aodh | ["v2"]
93
+ baremetal | ironic | ["v1"]
94
+ block_storage | cinder | ["v3", "v1"]
95
+ clustering | senlin | ["v1"]
96
+ compute | nova | ["v2.1"]
97
+ container | magnum | ["v1"]
98
+ data_processing | sahara | ["v1.1"]
99
+ data_protection | karbor | ["v1"]
100
+ database | trove | ["v1.0"]
101
+ dns | designate | ["v2"]
102
+ identity | keystone | ["v3", "v2.0"]
103
+ image | glance | ["v2", "v1"]
104
+ messaging | zaqar | ["v2"]
105
+ metering | ceilometer | ["v2"]
106
+ network | neutron | ["v2.0"]
107
+ object_storage | swift | ["v1"]
108
+ orchestration | heat | ["v1"]
109
+ search | searchlight | ["v1"]
110
+ shared_file_systems | manila | ["v2"]
111
+
112
+ * Notes
113
+ When an Openstack service requires a different service name, the :service_names option can be used (see below).
114
+
115
+ The #requests method provides the available requests for a service, for example:
116
+ ```ruby
117
+ openstack.compute.requests
118
+ ```
119
+
120
+ ## Setup
121
+
122
+ ### Authentication
123
+ The URL and credentials details are necessary to authenticate with the identity server (Keystone).
124
+
125
+ To provide a Keystone V3, which is the default recommended version:
126
+ ```ruby
127
+ auth = {
128
+ :url => "http://localhost:5000",
129
+ :user => "admin",
130
+ :password => "secret",
131
+ :project => "admin",
132
+ :domain => "default"
133
+ }
134
+ }
135
+ ```
136
+ Alternatively, for Keystone V2, just provide the tenant details, Misty will detect it's using Keystone V2:
137
+ ```ruby
138
+ auth = {
139
+ :url => "http://localhost:5000",
140
+ :user => "admin",
141
+ :password => "secret",
142
+ :tenant => "admin",
143
+ }
144
+ ```
145
+
146
+ ### Global options
147
+ Besides the authentication details, the following options which apply for the whole cloud
148
+
149
+ * :content_type
150
+ Format of the body of the successful HTTP responses to be JSON or Ruby structures.
151
+ Value is symbol. Allowed value is `:json`.
152
+ By default response body are converted to Ruby structures.
153
+ * :log_file
154
+ File name and path for log file.
155
+ Value is file path or IO object - Default is `./misty.log`.
156
+ For example: use STDOUT for terminal output or alternatively use '/dev/null' to avoid the logs entirely.
157
+ * :log_level
158
+ Value is Fixnum - Default is 1 (Logger::INFO) - See Logger from Ruby standard Library
159
+
160
+ ```ruby
161
+ openstack = Misty::Cloud.new(:auth => auth, :content_type => :ruby, :log_file => STDOUT)
162
+ ```
163
+
164
+ ### Services Options
165
+ Each service can get options specifically:
166
+
167
+ ```ruby
168
+ openstack = Misty::Cloud.new(:auth => auth, :identity => {}, :compute => {})
169
+ ```
170
+
171
+ The following options are available:
172
+ * :api_version
173
+ The latest supported version is used by default. See Misty.services to use another version.
174
+ Value is a STRING
175
+ * :base_path
176
+ Allows to force the base path for every URL requests.
177
+ Value is a STRING
178
+ * :base_url
179
+ Allows to force the base URL for every requests.
180
+ Value is a STRING
181
+ * :interface
182
+ Allows to provide an alternate interface. Allowed values are "public", "internal" or "admin"
183
+ Value is a STRING - Default = "public"
184
+ * :region_id
185
+ Value is a STRING
186
+ Default "regionOne"
187
+ * :service_names
188
+ Value is a STRING - Default is defined by Misty.services
189
+ Allows to use a difference name for the service. For instance "identity3" for the identity service.
190
+ * :ssl_verify_mode
191
+ Used in SSL mode (detected from the URI)
192
+ Value is a BOOLEAN - Default is `true`
193
+ * :version
194
+ Version to be used when microversion is supported by the service.
195
+ Value is a STRING - Default is `"CURRENT"`
196
+ Allowed values are "CURRENT", "LATEST", "SUPPORTED", or a version number such as "2.0" or "3"
197
+
198
+ Example:
199
+ ```ruby
200
+ openstack = Misty::Cloud.new(:auth => auth, :log_level => 0, :identity => {:region_id => "regionTwo"}, :compute => {:version => "2.27", :interface => "admin"})
201
+ ```
202
+
203
+ ## Direct REST HTTP Methods
204
+ To send requests directly use the 'get', 'delete', 'post' and 'put' methods directly:
205
+ ```ruby
206
+ openstack.network.post("/v2.0/qos/policies/48985e6b8da145699d411f12a3459fca/dscp_marking_rules", data)
207
+ ```
208
+ # Requirements
209
+
210
+ ## Ruby versions tested
211
+ * Ruby 2.4.0
212
+ * Ruby 2.3.3
213
+ * Ruby 2.3.0
214
+ * Ruby 2.2.0
215
+
216
+ # Contributing
217
+ Contributors are welcome and must adhere to the [Contributor covenant code of conduct](http://contributor-covenant.org/).
218
+
219
+ Please submit issues/bugs and patches on the [Misty repository](https://github.com/flystack/misty).
220
+
221
+ # Copyright
222
+ Copyright © 2007 Free Software Foundation, Inc. See [LICENSE](LICENSE.md) for details.
@@ -0,0 +1,8 @@
1
+ require 'logger'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'time'
5
+ require 'uri'
6
+ require 'misty/misty'
7
+ require 'misty/autoload'
8
+ require 'misty/cloud'
@@ -0,0 +1,60 @@
1
+ module Misty
2
+ class Auth
3
+ class AuthenticationError < StandardError; end
4
+ class CatalogError < StandardError; end
5
+ class TokenError < StandardError; end
6
+
7
+ class ExpiryError < RuntimeError; end
8
+ class CredentialsError < RuntimeError; end
9
+ class InitError < RuntimeError; end
10
+ class URLError < RuntimeError; end
11
+
12
+ def self.factory(options = {})
13
+ if options[:project]
14
+ return Misty::AuthV3.new(options)
15
+ elsif options[:tenant]
16
+ return Misty::AuthV2.new(options)
17
+ else
18
+ raise CredentialsError, "Cannot identify version from credentials"
19
+ end
20
+ end
21
+
22
+ attr_reader :catalog
23
+
24
+ def initialize(options)
25
+ raise CredentialsError unless credentials_valid?(options)
26
+ @credentials = scoped_credentials(options)
27
+ raise URLError, "No URL provided" unless options[:url] && !options[:url].empty?
28
+ @uri = URI.parse(options[:url])
29
+ @token = nil
30
+ setup(authenticate)
31
+ raise CatalogError, "No catalog provided during authentication" if @catalog.empty?
32
+ end
33
+
34
+ def authenticate
35
+ http = Net::HTTP.new(@uri.host, @uri.port)
36
+ response = http.post(self.class.path, @credentials.to_json, Misty::HEADER_JSON)
37
+ raise AuthenticationError, "Response code=#{response.code}, Msg=#{response.msg}" unless response.code =~ /200|201/
38
+ response
39
+ end
40
+
41
+ def expired?
42
+ raise ExpiryError, "Missing token expiration data" if @expires.nil? || @expires.empty?
43
+ Time.parse(@expires) < Time.now.utc
44
+ end
45
+
46
+ def get_endpoint(service_names, region, interface)
47
+ @catalog.each do |catalog|
48
+ if service_names.include? catalog["type"]
49
+ return catalog_endpoints(catalog["endpoints"], region, interface)
50
+ end
51
+ end
52
+ raise CatalogError, "No service found with either #{service_names} name, region #{region}, interface #{interface}"
53
+ end
54
+
55
+ def get_token
56
+ authenticate if expired?
57
+ @token
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,46 @@
1
+ require 'misty/auth'
2
+
3
+ module Misty
4
+ class AuthV2 < Misty::Auth
5
+ def self.path
6
+ "/v2.0/tokens"
7
+ end
8
+
9
+ def catalog_endpoints(endpoints, region, interface)
10
+ endpoints.each do |endpoint|
11
+ if endpoint["region"] == region && endpoint["#{interface}URL"]
12
+ return endpoint["#{interface}URL"]
13
+ end
14
+ end
15
+ end
16
+
17
+ def credentials_valid?(creds)
18
+ true if creds[:user] && creds[:password] && creds[:tenant]
19
+ end
20
+
21
+ def get_endpoint_url(endpoints, region, interface)
22
+ endpoint = endpoints.select { |ep| !ep[interface].empty? }
23
+ raise CatalogError, "No endpoint available for region '#{region}' and interface '#{interface}'" unless endpoint
24
+ endpoint[0][interface]
25
+ end
26
+
27
+ def setup(response)
28
+ payload = JSON.load(response.body)
29
+ @token = payload["access"]["token"]["id"]
30
+ @catalog = payload["access"]["serviceCatalog"]
31
+ @expires = payload["access"]["token"]["expires"]
32
+ end
33
+
34
+ def scoped_credentials(creds)
35
+ {
36
+ "auth": {
37
+ "passwordCredentials": {
38
+ "username": creds[:user],
39
+ "password": creds[:password]
40
+ },
41
+ "tenantName": creds[:tenant]
42
+ }
43
+ }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,58 @@
1
+ require 'misty/auth'
2
+
3
+ module Misty
4
+ class AuthV3 < Misty::Auth
5
+ def self.path
6
+ "/v3/auth/tokens"
7
+ end
8
+
9
+ def catalog_endpoints(endpoints, region, interface)
10
+ endpoints.each do |endpoint|
11
+ if endpoint["region_id"] == region && endpoint["interface"] == interface
12
+ return endpoint["url"]
13
+ end
14
+ end
15
+ end
16
+
17
+ def credentials_valid?(creds)
18
+ true if creds[:user] && creds[:password] && creds[:project]
19
+ end
20
+
21
+ def get_endpoint_url(endpoints, region, interface)
22
+ endpoint = endpoints.select { |ep| ep["region_id"] == region && ep["interface"] == interface }
23
+ raise CatalogError, "No endpoint available for region '#{region}' and interface '#{interface}'" unless endpoint
24
+ endpoint[0]["url"]
25
+ end
26
+
27
+ def setup(response)
28
+ payload = JSON.load(response.body)
29
+ @token = response["x-subject-token"]
30
+ @catalog = payload["token"]["catalog"]
31
+ @expires = payload["token"]["expires_at"]
32
+ end
33
+
34
+ def scoped_credentials(creds)
35
+ creds[:domain] ||= "default"
36
+ {
37
+ "auth": {
38
+ "identity": {
39
+ "methods": ["password"],
40
+ "password": {
41
+ "user": {
42
+ "name": creds[:user],
43
+ "domain": { "id": "default" },
44
+ "password": creds[:password]
45
+ }
46
+ }
47
+ },
48
+ "scope": {
49
+ "project": {
50
+ "name": creds[:project],
51
+ "domain": { "id": creds[:domain] }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,82 @@
1
+ module Misty
2
+ module Openstack
3
+ module Aodh
4
+ autoload :V2, "misty/openstack/aodh/v2"
5
+ end
6
+
7
+ module Ceilometer
8
+ autoload :V2, "misty/openstack/ceilometer/v2"
9
+ end
10
+
11
+ module Cinder
12
+ autoload :V1, "misty/openstack/cinder/v1"
13
+ autoload :V3, "misty/openstack/cinder/v3"
14
+ end
15
+
16
+ module Designate
17
+ autoload :V2, "misty/openstack/designate/v2"
18
+ end
19
+
20
+ module Glance
21
+ autoload :V1, "misty/openstack/glance/v1"
22
+ autoload :V2, "misty/openstack/glance/v2"
23
+ end
24
+
25
+ module Heat
26
+ autoload :V1, "misty/openstack/heat/v1"
27
+ end
28
+
29
+ module Ironic
30
+ autoload :V1, "misty/openstack/ironic/v1"
31
+ end
32
+
33
+ module Karbor
34
+ autoload :V1, "misty/openstack/karbor/v1"
35
+ end
36
+
37
+ module Keystone
38
+ autoload :V3, "misty/openstack/keystone/v3"
39
+ autoload :V2_0, "misty/openstack/keystone/v2_0"
40
+ end
41
+
42
+ module Magnum
43
+ autoload :V1, "misty/openstack/magnum/v1"
44
+ end
45
+
46
+ module Manila
47
+ autoload :V2, "misty/openstack/manila/v2"
48
+ end
49
+
50
+ module Neutron
51
+ autoload :V2_0, "misty/openstack/neutron/v2_0"
52
+ end
53
+
54
+ module Nova
55
+ autoload :V2_1, "misty/openstack/nova/v2_1"
56
+ end
57
+
58
+ module Sahara
59
+ autoload :V1_1, "misty/openstack/sahara/v1_1"
60
+ end
61
+
62
+ module Searchlight
63
+ autoload :V1, "misty/openstack/searchlight/v1"
64
+ end
65
+
66
+ module Senlin
67
+ autoload :V1, "misty/openstack/senlin/v1"
68
+ end
69
+
70
+ module Swift
71
+ autoload :V1, "misty/openstack/swift/v1"
72
+ end
73
+
74
+ module Trove
75
+ autoload :V1_0, "misty/openstack/trove/v1_0"
76
+ end
77
+
78
+ module Zaqar
79
+ autoload :V2, "misty/openstack/zaqar/v2"
80
+ end
81
+ end
82
+ end