Merge remote-tracking branch 'upstream/develop' into development

This commit is contained in:
Alfonso Juan Dillera
2013-09-02 12:23:07 +08:00
19 changed files with 248 additions and 98 deletions

View File

@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"] spec.require_paths = ["lib"]
spec.add_dependency 'faraday', '~> 0.8.0' spec.add_dependency 'faraday', '~> 0.8.0'
spec.add_dependency 'activesupport', '>= 3.2.8', '<= 4.0.0' spec.add_dependency 'activesupport', '>= 3.2.8'
spec.add_dependency 'thor', '~> 0.18.1' spec.add_dependency 'thor', '~> 0.18.1'
spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "bundler", "~> 1.3"

1
lib/aviator.rb Normal file
View File

@@ -0,0 +1 @@
require 'aviator/core'

View File

@@ -3,6 +3,7 @@ require 'active_support/inflector'
require 'faraday' require 'faraday'
require "aviator/version" require "aviator/version"
require "aviator/core/dsl"
require "aviator/core/request" require "aviator/core/request"
require "aviator/core/response" require "aviator/core/response"
require "aviator/core/service" require "aviator/core/service"

37
lib/aviator/core/dsl.rb Normal file
View File

@@ -0,0 +1,37 @@
module Aviator
class << self
def define_request(request_name, &block)
class_obj = Class.new(Request, &block)
build_or_get_request_class(
Aviator,
class_obj,
class_obj.provider,
class_obj.service,
class_obj.api_version,
class_obj.endpoint_type,
request_name
)
end
private
def build_or_get_request_class(base, obj, *hierarchy)
const_name = hierarchy.shift.to_s.camelize
const = if base.const_defined?(const_name)
base.const_get(const_name)
else
base.const_set(const_name, (hierarchy.empty? ? obj : Module.new))
end
hierarchy.empty? ? const : build_or_get_request_class(const, obj, *hierarchy)
end
end # class << self
end # module Aviator

View File

@@ -191,6 +191,15 @@ module Aviator
end end
def provider(value=nil)
if value
@provider = value
else
@provider
end
end
def optional_params def optional_params
@optional_params ||= [] @optional_params ||= []
end end
@@ -206,6 +215,15 @@ module Aviator
end end
def service(value=nil)
if value
@service = value
else
@service
end
end
def url? def url?
instance_methods.include? :url instance_methods.include? :url
end end

View File

@@ -45,29 +45,6 @@ module Aviator
end end
end end
# Because we define requests in a flattened scope, we want to make sure that when each
# request is initialized it doesn't get polluted by instance variables and methods
# of the containing class. This builder class makes that happen by being a
# scope gate for the file. See Metaprogramming Ruby, specifically on blocks and scope
class RequestBuilder
# This method gets called by the request file eval'd in self.build below
def define_request(request_name, &block)
klass = Class.new(Aviator::Request, &block)
return klass, request_name
end
def self.build(path_to_request_file)
clean_room = new
clean_room.instance_eval(File.read(path_to_request_file))
end
private_class_method :new
end
attr_accessor :default_session_data attr_accessor :default_session_data
@@ -134,19 +111,27 @@ module Aviator
# Candidate for extraction to aviator/openstack # Candidate for extraction to aviator/openstack
def find_request(name, session_data, endpoint_type=nil) def find_request(name, session_data, endpoint_type=nil)
endpoint_types = if endpoint_type endpoint_types = if endpoint_type
[endpoint_type.to_sym] [endpoint_type.to_s.camelize]
else else
[:public, :admin] ['Public', 'Admin']
end end
version = infer_version(session_data) namespace = Aviator.const_get(provider.camelize)
.const_get(service.camelize)
return nil unless version && requests[version] version = infer_version(session_data).to_s.camelize
return nil unless version && namespace.const_defined?(version)
namespace = namespace.const_get(version)
endpoint_types.each do |endpoint_type| endpoint_types.each do |endpoint_type|
next unless requests[version][endpoint_type] name = name.to_s.camelize
pair = requests[version][endpoint_type].find{ |k, v| k == name }
return pair[1] unless pair.nil? next unless namespace.const_defined?(endpoint_type)
next unless namespace.const_get(endpoint_type).const_defined?(name)
return namespace.const_get(endpoint_type).const_get(name)
end end
nil nil
@@ -182,22 +167,14 @@ module Aviator
).expand_path ).expand_path
) )
@requests ||= {} request_file_paths.each{ |path| require path }
request_file_paths.each do |path_to_file|
klass, request_name = RequestBuilder.build(path_to_file)
api_version = @requests[klass.api_version] ||= {}
endpoint_type = api_version[klass.endpoint_type] ||= {}
endpoint_type[request_name] = klass
end
end end
def log_file def log_file
@log_file @log_file
end end
end end
end end

View File

@@ -1,7 +1,10 @@
define_request :list_images do Aviator.define_request :list_images do
endpoint_type :public provider :openstack
service :compute
api_version :v2 api_version :v2
endpoint_type :public
http_method :get http_method :get
link_to 'documentation', link_to 'documentation',

View File

@@ -1,7 +1,10 @@
define_request :create_tenant do Aviator.define_request :create_tenant do
endpoint_type :admin provider :openstack
service :identity
api_version :v2 api_version :v2
endpoint_type :admin
http_method :post http_method :post
link_to 'documentation', link_to 'documentation',

View File

@@ -1,7 +1,10 @@
define_request :list_tenants do Aviator.define_request :list_tenants do
endpoint_type :admin provider :openstack
service :identity
api_version :v2 api_version :v2
endpoint_type :admin
http_method :get http_method :get
link_to 'documentation', link_to 'documentation',

View File

@@ -1,9 +1,12 @@
define_request :create_token do Aviator.define_request :create_token do
anonymous anonymous
endpoint_type :public provider :openstack
service :identity
api_version :v2 api_version :v2
endpoint_type :public
http_method :post http_method :post
link_to 'documentation', link_to 'documentation',

View File

@@ -1,7 +1,10 @@
define_request :list_tenants do Aviator.define_request :list_tenants do
endpoint_type :public provider :openstack
service :identity
api_version :v2 api_version :v2
endpoint_type :public
http_method :get http_method :get
link_to 'documentation', link_to 'documentation',

83
test.rb Normal file
View File

@@ -0,0 +1,83 @@
require 'pry'
require 'active_support/inflector'
module Aviator
class << self
def define_request(request_name, &block)
class_obj = Class.new(Object, &block)
build_or_get_request_class(
Aviator,
class_obj,
class_obj.provider,
class_obj.service,
class_obj.api_version,
class_obj.endpoint_type,
request_name
)
end
private
def build_or_get_request_class(base, obj, *hierarchy)
const_name = hierarchy.shift.to_s.classify
const = if base.const_defined?(const_name)
base.const_get(const_name)
else
base.const_set(const_name, (hierarchy.empty? ? obj : Module.new))
end
hierarchy.empty? ? const : build_or_get_request_class(const, obj, *hierarchy)
end
end # class << self
end # module Aviator
test1 = Aviator.define_request :test1 do
def self.provider
:openstack
end
def self.service
:identity
end
def self.api_version
:v2
end
def self.endpoint_type
:admin
end
end
test2 = Aviator.define_request :test2 do
def self.provider
:openstack
end
def self.service
:identity
end
def self.api_version
:v3
end
def self.endpoint_type
:admin
end
end
binding.pry

View File

@@ -22,21 +22,21 @@ class Aviator::Test
end end
end end
end end
def klass def klass
Aviator::Service Aviator::Service
end end
def service(default_session_data=nil) def service(default_session_data=nil)
options = { options = {
provider: config[:provider], provider: config[:provider],
service: config[:auth_service][:name] service: config[:auth_service][:name]
} }
options[:default_session_data] = default_session_data unless default_session_data.nil? options[:default_session_data] = default_session_data unless default_session_data.nil?
klass.new(options) klass.new(options)
end end
@@ -45,7 +45,7 @@ class Aviator::Test
it 'can find the correct request based on bootstrapped session data' do it 'can find the correct request based on bootstrapped session data' do
response = do_auth_request response = do_auth_request
response.must_be_instance_of Aviator::Response response.must_be_instance_of Aviator::Response
response.request.api_version.must_equal config[:auth_service][:api_version].to_sym response.request.api_version.must_equal config[:auth_service][:api_version].to_sym
end end
@@ -67,26 +67,26 @@ class Aviator::Test
params[k] = v params[k] = v
end end
end end
response.must_be_instance_of Aviator::Response response.must_be_instance_of Aviator::Response
response.request.api_version.must_equal :v2 response.request.api_version.must_equal :v2
response.status.must_equal 200 response.status.must_equal 200
end end
it 'can find the correct request based on non-bootstrapped session data' do it 'can find the correct request based on non-bootstrapped session data' do
session_data = do_auth_request.body session_data = do_auth_request.body
response = service.request :create_tenant, session_data: session_data do |params| response = service.request :create_tenant, session_data: session_data do |params|
params.name = 'Test Project' params.name = 'Test Project'
params.description = 'This is a test' params.description = 'This is a test'
params.enabled = true params.enabled = true
end end
response.status.must_equal 200 response.status.must_equal 200
end end
it 'uses the default session data if session data is not provided' do it 'uses the default session data if session data is not provided' do
default_session_data = do_auth_request.body default_session_data = do_auth_request.body
s = service(default_session_data) s = service(default_session_data)
@@ -96,11 +96,11 @@ class Aviator::Test
params.description = 'This is a test' params.description = 'This is a test'
params.enabled = true params.enabled = true
end end
response.status.must_equal 200 response.status.must_equal 200
end end
it 'raises a SessionDataNotProvidedError if there is no session data' do it 'raises a SessionDataNotProvidedError if there is no session data' do
the_method = lambda do the_method = lambda do
service.request :create_tenant do |params| service.request :create_tenant do |params|
@@ -109,28 +109,28 @@ class Aviator::Test
params.enabled = true params.enabled = true
end end
end end
the_method.must_raise Aviator::Service::SessionDataNotProvidedError the_method.must_raise Aviator::Service::SessionDataNotProvidedError
error = the_method.call rescue $! error = the_method.call rescue $!
error.message.wont_be_nil error.message.wont_be_nil
end end
it 'accepts an endpoint type option for selecting a specific request' do it 'accepts an endpoint type option for selecting a specific request' do
default_session_data = do_auth_request.body default_session_data = do_auth_request.body
s = service(default_session_data) s = service(default_session_data)
response1 = s.request :list_tenants, endpoint_type: 'admin' response1 = s.request :list_tenants, endpoint_type: 'admin'
response2 = s.request :list_tenants, endpoint_type: 'public' response2 = s.request :list_tenants, endpoint_type: 'public'
response1.request.url.wont_equal response2.request.url response1.request.url.wont_equal response2.request.url
end end
end end
describe '#default_session_data=' do describe '#default_session_data=' do
it 'sets the service\'s default session data' do it 'sets the service\'s default session data' do
bootstrap = { bootstrap = {
auth_service: { auth_service: {
@@ -139,18 +139,18 @@ class Aviator::Test
request: 'create_token' request: 'create_token'
} }
} }
svc = service(bootstrap) svc = service(bootstrap)
session_data_1 = svc.default_session_data session_data_1 = svc.default_session_data
session_data_2 = do_auth_request.body session_data_2 = do_auth_request.body
svc.default_session_data = session_data_2 svc.default_session_data = session_data_2
svc.default_session_data.wont_equal session_data_1 svc.default_session_data.wont_equal session_data_1
svc.default_session_data.must_equal session_data_2 svc.default_session_data.must_equal session_data_2
end end
end end
end end

View File

@@ -32,9 +32,7 @@ class Aviator::Test
def klass def klass
path = helper.request_path('compute', 'v2', 'public', 'list_images.rb') @klass ||= helper.load_request('openstack', 'compute', 'v2', 'public', 'list_images.rb')
klass, request_name = Aviator::Service::RequestBuilder.build(path)
klass
end end
@@ -105,13 +103,13 @@ class Aviator::Test
[ :status, 'ACTIVE' ], [ :status, 'ACTIVE' ],
[ :type, 'application/vnd.openstack.image' ] [ :type, 'application/vnd.openstack.image' ]
] ]
url += "/detail" if params.first[1] url += "/detail" if params.first[1]
filters = [] filters = []
params[1, params.length-1].each { |pair| filters << "#{ pair[0] }=#{ pair[1] }" } params[1, params.length-1].each { |pair| filters << "#{ pair[0] }=#{ pair[1] }" }
url += "?#{ filters.join('&') }" unless filters.empty? url += "?#{ filters.join('&') }" unless filters.empty?
request = klass.new(session_data) do |p| request = klass.new(session_data) do |p|
@@ -154,8 +152,8 @@ class Aviator::Test
response.body[:images].length.must_equal 0 response.body[:images].length.must_equal 0
response.headers.wont_be_nil response.headers.wont_be_nil
end end
validate_response 'parameters are valid' do validate_response 'parameters are valid' do
service = Aviator::Service.new( service = Aviator::Service.new(
provider: 'openstack', provider: 'openstack',

View File

@@ -36,9 +36,7 @@ class Aviator::Test
def klass def klass
path = helper.request_path('identity', 'v2', 'admin', 'create_tenant.rb') @klass ||= helper.load_request('openstack', 'identity', 'v2', 'admin', 'create_tenant.rb')
klass, request_name = Aviator::Service::RequestBuilder.build(path)
klass
end end

View File

@@ -18,9 +18,7 @@ class Aviator::Test
def klass def klass
path = helper.request_path('identity', 'v2', 'public', 'create_token.rb') @klass ||= helper.load_request('openstack', 'identity', 'v2', 'public', 'create_token.rb')
klass, request_name = Aviator::Service::RequestBuilder.build(path)
klass
end end

View File

@@ -44,12 +44,31 @@ class Test
auth_service: Environment.openstack_admin[:auth_service] auth_service: Environment.openstack_admin[:auth_service]
} }
end end
def get_request_class(parent, *path)
const_name = path.shift.to_s.camelize.gsub(/\.rb$/, '')
const = if parent.const_defined?(const_name)
parent.const_get(const_name)
else
raise "Constant #{ const_name } could not be found."
end
path.empty? ? const : get_request_class(const, *path)
end
def load_request(*path)
require request_path(*path)
get_request_class(Aviator, *path)
end
def request_path(*path) def request_path(*path)
Pathname.new(__FILE__).join('..', '..', '..', 'lib', 'aviator', 'openstack').expand_path.join(*path) Pathname.new(__FILE__).join('..', '..', '..', 'lib', 'aviator').expand_path.join(*path)
end end
end end
end end

View File

@@ -1,6 +1,7 @@
require "minitest/reporters" require "minitest/reporters"
module MiniTest::Reporters::RedStack module Aviator
class Test
class SpecReporter < MiniTest::Reporters::SpecReporter class SpecReporter < MiniTest::Reporters::SpecReporter
@@ -41,5 +42,6 @@ module MiniTest::Reporters::RedStack
end end
end end
end
MiniTest::Reporters.use! MiniTest::Reporters::RedStack::ProgressReporter.new MiniTest::Reporters.use! Aviator::Test::ProgressReporter.new

View File

@@ -12,6 +12,9 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
] ]
SimpleCov.start do SimpleCov.start do
add_filter '/test/' add_filter '/test/'
add_group 'Core', 'lib/aviator/core'
add_group 'OpenStack', 'lib/aviator/openstack'
end end
require 'minitest/autorun' require 'minitest/autorun'
@@ -32,4 +35,4 @@ Dir[Pathname.new(__FILE__).join('..', 'support', '*.rb')].each do |f|
require f require f
end end
require 'aviator/core' require 'aviator'