These patches add support for optional features to Deltacloud API. Features are declared in lib/deltacloud/base_driver/features.rb; for example,
declare_feature :instances, :user_name do description "Accept a user-defined name on instance creation" operation :create do param :name, :string, :optional, nil, "The user-defined name" end end
declares the feature 'user_name' for the instances collection (features are scoped by collections, there's not much point in sharing them across collections). The feature declaration describes what happens to the API when a driver supports this feature - for user_name, the create operation for instances will accept an additional parameter 'name'.
The driver then supports the feature by simply including the line
feature :instances, :user_name
That will make sure operations are modified according to the feature declaration (including docs ;) It will also cause that feature to be advertised in the API XML:
<api driver="mock" version="1.0"> ... <link href="http://localhost:3000/api/instances" rel="instances"> <feature name="user_name"/> </link> ... </api>
Finally, patches 8/9 and 9/9 add feature support to the client: the client now has a method feature?(collection, feature_name), and deltacloudc uses that to reject user-specified names on instance creation when the driver does not support that feature (funnily enough, it's pretty much only EC2 that does _not_ support that)
Besides user_name, there's also a user_data feature for EC2-style data injection. I initially thought that the api.xml should describe in more detail how a particular feature is supported (e.g., the maximum length of the instance name for user_name, the size of the data for user_data), but it seems that that is overkill for now - it's easy enough to add that in later without breaking API compatibility.
David
From: David Lutterkort lutter@redhat.com
It's perfectly valid to try and list by an architecture that is not i386 or x86_64. That should just return an empty list. --- server/libexec/server.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/server/libexec/server.rb b/server/libexec/server.rb index 95c532e..fae19c9 100644 --- a/server/libexec/server.rb +++ b/server/libexec/server.rb @@ -121,7 +121,7 @@ collection :images do description 'The instances collection will return a set of all images available to the current use. You can filter images using "owner_id" and "architecture" parameter' param :id, :string param :owner_id, :string - param :architecture, :string, :optional, [ 'i386', 'x86_64' ] + param :architecture, :string, :optional control { filter_all(:images) } end
From: David Lutterkort lutter@redhat.com
Also fixes a bug where parameters with a fixed set of values were never validated against those values. The issue was a naming problem in Operation.control in rabbit.rb --- server/libexec/lib/deltacloud/validation.rb | 61 +++++++++++++++++++++++++ server/libexec/lib/sinatra/rabbit.rb | 22 ++------- server/libexec/lib/sinatra/validation.rb | 28 ----------- server/libexec/server.rb | 15 ++++++- server/libexec/views/docs/collection.xml.haml | 10 ++-- server/libexec/views/docs/operation.html.haml | 10 ++-- server/libexec/views/docs/operation.xml.haml | 10 ++-- server/libexec/views/error.html.haml | 7 --- server/libexec/views/error.xml.haml | 7 +++ 9 files changed, 102 insertions(+), 68 deletions(-) create mode 100644 server/libexec/lib/deltacloud/validation.rb delete mode 100644 server/libexec/lib/sinatra/validation.rb delete mode 100644 server/libexec/views/error.html.haml create mode 100644 server/libexec/views/error.xml.haml
diff --git a/server/libexec/lib/deltacloud/validation.rb b/server/libexec/lib/deltacloud/validation.rb new file mode 100644 index 0000000..df1e66c --- /dev/null +++ b/server/libexec/lib/deltacloud/validation.rb @@ -0,0 +1,61 @@ +module Deltacloud::Validation + + class Failure < StandardError + attr_reader :param + def initialize(param, msg='') + super(msg) + @param = param + end + + def name + param.name + end + end + + class Param + attr_reader :name, :klass, :type, :options, :description + + def initialize(args) + @name = args[0] + @klass = args[1] || :string + @type = args[2] || :optional + @options = args[3] || [] + @description = args[4] || '' + end + + def required? + type.eql?(:required) + end + + def optional? + type.eql?(:optional) + end + end + + def param(*args) + raise DuplicateParamException if params[args[0]] + p = Param.new(args) + params[p.name] = p + end + + def params + @params ||= {} + @params + end + + def each_param(&block) + params.each_value { |p| yield p } + end + + def validate(values) + each_param do |p| + if p.required? and not values[p.name] + raise Failure.new(p, "Required parameter #{p.name} not found") + end + if values[p.name] and not p.options.empty? and + not p.options.include?(values[p.name]) + raise Failure.new(p, "Parameter #{p.name} has value #{values[p.name]} which is not in #{p.options.join(", ")}") + end + end + end +end diff --git a/server/libexec/lib/sinatra/rabbit.rb b/server/libexec/lib/sinatra/rabbit.rb index 4a51a4c..afaa865 100644 --- a/server/libexec/lib/sinatra/rabbit.rb +++ b/server/libexec/lib/sinatra/rabbit.rb @@ -1,5 +1,6 @@ require 'sinatra/base' require 'sinatra/url_for' +require 'deltacloud/validation'
module Sinatra
@@ -8,11 +9,12 @@ module Sinatra class DuplicateParamException < Exception; end class DuplicateOperationException < Exception; end class DuplicateCollectionException < Exception; end - class ValidationFailure < Exception; end
class Operation attr_reader :name, :method
+ include ::Deltacloud::Validation + STANDARD = { :index => { :method => :get, :member => false }, :show => { :method => :get, :member => true }, @@ -29,7 +31,6 @@ module Sinatra @method = opts[:method].to_sym @member = opts[:member] @description = "" - @params = {} instance_eval(&block) if block_given? generate_documentation end @@ -54,23 +55,10 @@ module Sinatra end end
- def param(*args) - raise DuplicateParamException if @params[args[0]] - spec = { - :class => args[1] || :string, - :type => args[2] || :optional, - :options => args[3] || [], - :description => args[4] || '' } - @params[args[0]] = spec - end - - def params - @params - end - def control(&block) + op = self @control = Proc.new do - validate_parameters(params, @params) + op.validate(params) instance_eval(&block) end end diff --git a/server/libexec/lib/sinatra/validation.rb b/server/libexec/lib/sinatra/validation.rb deleted file mode 100644 index c16c32e..0000000 --- a/server/libexec/lib/sinatra/validation.rb +++ /dev/null @@ -1,28 +0,0 @@ -class ValidationFailure < Exception - attr_reader :name, :spec, :msg - def initialize(name, spec, msg='') - @name, @spec, @msg = name, spec, msg - end -end - -error ValidationFailure do - content_type 'text/xml', :charset => 'utf-8' - @error = request.env['sinatra.error'] - haml :error, :layout => false -end - -def validate_parameters(values, parameters) - require 'pp' - parameters.each_key do |p| - if parameters[p][:type].eql?(:required) and not values[p.to_s] - raise ValidationFailure.new(p, parameters[p], 'Required parameter not found') - end - if parameters[p][:type].eql?(:required) and not parameters[p][:options].empty? and not parameters[p][:options].include?(values[p.to_s]) - raise ValidationFailure.new(p, parameters[p], 'Wrong value for required parameter') - end - if parameters[p][:type].eql?(:optional) and not parameters[p][:options].empty? and - not values[p.to_s].nil? and not parameters[p][:options].include?(values[p.to_s]) - raise ValidationFailure.new(p, parameters[p], 'Wrong value for optional parameter') - end - end -end diff --git a/server/libexec/server.rb b/server/libexec/server.rb index fae19c9..c730c71 100644 --- a/server/libexec/server.rb +++ b/server/libexec/server.rb @@ -9,8 +9,8 @@ require 'builder' require 'drivers' require 'sinatra/static_assets' require 'sinatra/rabbit' -require 'sinatra/validation' require 'sinatra/lazy_auth' +require 'deltacloud/validation'
configure do set :raise_errors => false @@ -64,6 +64,19 @@ def show(model) end end
+ +# +# Error handlers +# +error Deltacloud::Validation::Failure do + @error = request.env['sinatra.error'] + $stdout.flush + response.status = 400 + respond_to do |format| + format.xml { haml :error, :layout => false } + end +end + # Redirect to /api get '/' do redirect '/api'; end
diff --git a/server/libexec/views/docs/collection.xml.haml b/server/libexec/views/docs/collection.xml.haml index f8dbb37..df25091 100644 --- a/server/libexec/views/docs/collection.xml.haml +++ b/server/libexec/views/docs/collection.xml.haml @@ -5,10 +5,10 @@ - @operations.keys.sort_by { |k| k.to_s }.each do |operation| %operation{:url => "/api/#{@collection.name.to_s}", :name => "#{operation}", :href => "#{@operations[operation].path}", :method => "#{@operations[operation].method}"} %description #{@operations[operation].description} - - @operations[operation].params.each_key do |p| - %parameter{:name => "#{p}", :type => "#{@operations[operation].params[p][:type]}"} - %class #{@operations[operation].params[p][:class]} - - unless @operations[operation].params[p][:options].empty? + - @operations[operation].each_param do |param| + %parameter{:name => "#{param.name}", :type => "#{param.type}"} + %class #{param.klass} + - unless param.options.empty? %values - - @operations[operation].params[p][:options].each do |v| + - param.options.each do |v| %value #{v} diff --git a/server/libexec/views/docs/operation.html.haml b/server/libexec/views/docs/operation.html.haml index d7f80c2..ec6374b 100644 --- a/server/libexec/views/docs/operation.html.haml +++ b/server/libexec/views/docs/operation.html.haml @@ -22,10 +22,10 @@ %th Class %th Valid values %tbody - - @operation.params.each_key do |p| + - @operation.each_param do |p| %tr %td{:style => "width:15em"} - %em #{p} - %td{:style => "width:10em"} #{@operation.params[p][:type]} - %td #{@operation.params[p][:class]} - %td{:style => "width:10em"} #{@operation.params[p][:options].join(',')} + %em #{p.name} + %td{:style => "width:10em"} #{p.type} + %td #{p.klass} + %td{:style => "width:10em"} #{p.options.join(',')} diff --git a/server/libexec/views/docs/operation.xml.haml b/server/libexec/views/docs/operation.xml.haml index 3c4ef42..ca4687a 100644 --- a/server/libexec/views/docs/operation.xml.haml +++ b/server/libexec/views/docs/operation.xml.haml @@ -1,10 +1,10 @@ %docs{:status => "unsupported"} %operation{:url => "/api/docs/#{@collection.name.to_s}", :name => "#{@operation.name.to_s}", :href => "#{@operation.path}", :method => "#{@operation.method}"} %description #{@operation.description} - - @operation.params.each_key do |p| - %parameter{:name => "#{p}", :type => "#{@operation.params[p][:type]}"} - %class #{@operation.params[p][:class]} - - unless @operation.params[p][:options].empty? + - @operation.each_param do |param| + %parameter{:name => "#{param.name}", :type => "#{param.type}"} + %class #{param.klass} + - unless param.options.empty? %values - - @operation.params[p][:options].each do |v| + - param.options.each do |v| %value #{v} diff --git a/server/libexec/views/error.html.haml b/server/libexec/views/error.html.haml deleted file mode 100644 index a9d7fc6..0000000 --- a/server/libexec/views/error.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%error{:url => "#{request.env['REQUEST_URI']}"} - %parameter #{@error.name} - %msg #{@error.msg} - - unless @error.spec[:options].empty? - %valid_options - - @error.spec[:options].each do |v| - %value #{v} diff --git a/server/libexec/views/error.xml.haml b/server/libexec/views/error.xml.haml new file mode 100644 index 0000000..d7ef1af --- /dev/null +++ b/server/libexec/views/error.xml.haml @@ -0,0 +1,7 @@ +%error{:url => "#{request.env['REQUEST_URI']}"} + %parameter #{@error.name} + %message #{@error.message} + - unless @error.param.options.empty? + %valid_options + - @error.param.options.each do |v| + %value #{v}
From: David Lutterkort lutter@redhat.com
Set up the structure for adding feature support to the base driver --- server/libexec/lib/deltacloud/base_driver.rb | 142 +------------------ .../lib/deltacloud/base_driver/base_driver.rb | 157 ++++++++++++++++++++ 2 files changed, 158 insertions(+), 141 deletions(-) create mode 100644 server/libexec/lib/deltacloud/base_driver/base_driver.rb
diff --git a/server/libexec/lib/deltacloud/base_driver.rb b/server/libexec/lib/deltacloud/base_driver.rb index 5add111..5ead03d 100644 --- a/server/libexec/lib/deltacloud/base_driver.rb +++ b/server/libexec/lib/deltacloud/base_driver.rb @@ -15,144 +15,4 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- -module Deltacloud - - class AuthException < Exception - end - - class BaseDriver - - def self.define_hardware_profile(name,&block) - @hardware_profiles ||= [] - hw_profile = @hardware_profiles.find{|e| e.name == name} - return if hw_profile - hw_profile = ::Deltacloud::HardwareProfile.new( name, &block ) - puts hw_profile.inspect - @hardware_profiles << hw_profile - end - - def self.hardware_profiles - @hardware_profiles ||= [] - @hardware_profiles - end - - def hardware_profiles - self.class.hardware_profiles - end - - def hardware_profile(name) - self.class.hardware_profiles.find{|e| e.name == name } - end - - def self.define_instance_states(&block) - machine = ::Deltacloud::StateMachine.new(&block) - @instance_state_machine = machine - end - - def self.instance_state_machine - @instance_state_machine - end - - def instance_state_machine - self.class.instance_state_machine - end - - def instance_actions_for(state) - actions = [] - state_key = state.downcase.to_sym - states = instance_state_machine.states() - current_state = states.find{|e| e.name == state.underscore.to_sym } - if ( current_state ) - actions = current_state.transitions.collect{|e|e.action} - actions.reject!{|e| e.nil?} - end - actions - end - - def flavor(credentials, opts) - flavors = flavors(credentials, opts) - return flavors.first unless flavors.empty? - nil - end - - def flavors(credentials, ops) - [] - end - - def flavors_by_architecture(credentials, architecture) - flavors(credentials, :architecture => architecture) - end - - def realm(credentials, opts) - realms = realms(credentials, opts) - return realms.first unless realms.empty? - nil - end - - def realms(credentials, opts=nil) - [] - end - - def image(credentials, opts) - images = images(credentials, opts) - return images.first unless images.empty? - nil - end - - def images(credentials, ops) - [] - end - - def instance(credentials, opts) - instances = instances(credentials, opts) - return instances.first unless instances.empty? - nil - end - - def instances(credentials, ops) - [] - end - - def create_instance(credentials, image_id, opts) - end - def start_instance(credentials, id) - end - def stop_instance(credentials, id) - end - def reboot_instance(credentials, id) - end - - def storage_volume(credentials, opts) - volumes = storage_volumes(credentials, opts) - return volumes.first unless volumes.empty? - nil - end - - def storage_volumes(credentials, ops) - [] - end - - def storage_snapshot(credentials, opts) - snapshots = storage_snapshots(credentials, opts) - return snapshots.first unless snapshots.empty? - nil - end - - def storage_snapshots(credentials, ops) - [] - end - - def filter_on(collection, attribute, opts) - return collection if opts.nil? - return collection if opts[attribute].nil? - filter = opts[attribute] - if ( filter.is_a?( Array ) ) - return collection.select{|e| filter.include?( e.send(attribute) ) } - else - return collection.select{|e| filter == e.send(attribute) } - end - end - end - -end +require 'deltacloud/base_driver/base_driver' diff --git a/server/libexec/lib/deltacloud/base_driver/base_driver.rb b/server/libexec/lib/deltacloud/base_driver/base_driver.rb new file mode 100644 index 0000000..eab64a8 --- /dev/null +++ b/server/libexec/lib/deltacloud/base_driver/base_driver.rb @@ -0,0 +1,157 @@ +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +module Deltacloud + + class AuthException < Exception + end + + class BaseDriver + + def self.define_hardware_profile(name,&block) + @hardware_profiles ||= [] + hw_profile = @hardware_profiles.find{|e| e.name == name} + return if hw_profile + hw_profile = ::Deltacloud::HardwareProfile.new( name, &block ) + puts hw_profile.inspect + @hardware_profiles << hw_profile + end + + def self.hardware_profiles + @hardware_profiles ||= [] + @hardware_profiles + end + + def hardware_profiles + self.class.hardware_profiles + end + + def hardware_profile(name) + self.class.hardware_profiles.find{|e| e.name == name } + end + + def self.define_instance_states(&block) + machine = ::Deltacloud::StateMachine.new(&block) + @instance_state_machine = machine + end + + def self.instance_state_machine + @instance_state_machine + end + + def instance_state_machine + self.class.instance_state_machine + end + + def instance_actions_for(state) + actions = [] + state_key = state.downcase.to_sym + states = instance_state_machine.states() + current_state = states.find{|e| e.name == state.underscore.to_sym } + if ( current_state ) + actions = current_state.transitions.collect{|e|e.action} + actions.reject!{|e| e.nil?} + end + actions + end + + def flavor(credentials, opts) + flavors = flavors(credentials, opts) + return flavors.first unless flavors.empty? + nil + end + + def flavors(credentials, ops) + [] + end + + def flavors_by_architecture(credentials, architecture) + flavors(credentials, :architecture => architecture) + end + + def realm(credentials, opts) + realms = realms(credentials, opts) + return realms.first unless realms.empty? + nil + end + + def realms(credentials, opts=nil) + [] + end + + def image(credentials, opts) + images = images(credentials, opts) + return images.first unless images.empty? + nil + end + + def images(credentials, ops) + [] + end + + def instance(credentials, opts) + instances = instances(credentials, opts) + return instances.first unless instances.empty? + nil + end + + def instances(credentials, ops) + [] + end + + def create_instance(credentials, image_id, opts) + end + def start_instance(credentials, id) + end + def stop_instance(credentials, id) + end + def reboot_instance(credentials, id) + end + + def storage_volume(credentials, opts) + volumes = storage_volumes(credentials, opts) + return volumes.first unless volumes.empty? + nil + end + + def storage_volumes(credentials, ops) + [] + end + + def storage_snapshot(credentials, opts) + snapshots = storage_snapshots(credentials, opts) + return snapshots.first unless snapshots.empty? + nil + end + + def storage_snapshots(credentials, ops) + [] + end + + def filter_on(collection, attribute, opts) + return collection if opts.nil? + return collection if opts[attribute].nil? + filter = opts[attribute] + if ( filter.is_a?( Array ) ) + return collection.select{|e| filter.include?( e.send(attribute) ) } + else + return collection.select{|e| filter == e.send(attribute) } + end + end + end + +end
From: David Lutterkort lutter@redhat.com
* base_driver: add features and support for defining them in drivers * views (xml): list features for each collection in api.xml --- server/libexec/lib/deltacloud/base_driver.rb | 1 + .../libexec/lib/deltacloud/base_driver/features.rb | 114 ++++++++++++++++++++ server/libexec/views/api/show.xml.haml | 2 + 3 files changed, 117 insertions(+), 0 deletions(-) create mode 100644 server/libexec/lib/deltacloud/base_driver/features.rb
diff --git a/server/libexec/lib/deltacloud/base_driver.rb b/server/libexec/lib/deltacloud/base_driver.rb index 5ead03d..3a8656d 100644 --- a/server/libexec/lib/deltacloud/base_driver.rb +++ b/server/libexec/lib/deltacloud/base_driver.rb @@ -16,3 +16,4 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
require 'deltacloud/base_driver/base_driver' +require 'deltacloud/base_driver/features' diff --git a/server/libexec/lib/deltacloud/base_driver/features.rb b/server/libexec/lib/deltacloud/base_driver/features.rb new file mode 100644 index 0000000..e626d91 --- /dev/null +++ b/server/libexec/lib/deltacloud/base_driver/features.rb @@ -0,0 +1,114 @@ +require 'deltacloud/validation' + +# Add advertising of optional features to the base driver +module Deltacloud + + class FeatureError < StandardError; end + class DuplicateFeatureDeclError < FeatureError; end + class DuplicateFeatureError < FeatureError; end + class UndeclaredFeatureError < FeatureError; end + + class BaseDriver + + # An operation on a collection like cretae or show. Features + # can add parameters to operations + class Operation + attr_reader :name + + include Deltacloud::Validation + + def initialize(name, &block) + @name = name + @params = {} + instance_eval &block + end + end + + # The declaration of a feature, defines what operations + # are modified by it + class FeatureDecl + attr_reader :name, :operations + + def initialize(name, &block) + @name = name + @operations = [] + instance_eval &block + end + + def description(text=nil) + @description = text if text + @description + end + + def operation(name, &block) + @operations << Operation.new(name, &block) + end + end + + # A specific feature enabled by a driver (see +feature+) + class Feature + attr_reader :decl + + def initialize(decl, &block) + @decl = decl + instance_eval &block if block_given? + end + + def name + decl.name + end + + def operations + decl.operations + end + + def description + decl.description + end + end + + def self.feature_decls + @@feature_decls ||= {} + end + + def self.feature_decl_for(collection, name) + decls = feature_decls[collection] + if decls + decls.find { |dcl| dcl.name == name } + else + nil + end + end + + # Declare a new feature + def self.declare_feature(collection, name, &block) + feature_decls[collection] ||= [] + raise DuplicateFeatureDeclError if feature_decl_for(collection, name) + feature_decls[collection] << FeatureDecl.new(name, &block) + end + + def self.features + @@features ||= {} + end + + # Declare in a driver that it supports a specific feature + def self.feature(collection, name, &block) + features[collection] ||= [] + if features[collection].find { |f| f.name == name } + raise DuplicateFeatureError + end + unless decl = feature_decl_for(collection, name) + raise UndeclaredFeatureError, "No feature #{name} for #{collection}" + end + features[collection] << Feature.new(decl, &block) + end + + def features(collection) + self.class.features[collection] || [] + end + + # + # Declaration of optional features + # + end +end diff --git a/server/libexec/views/api/show.xml.haml b/server/libexec/views/api/show.xml.haml index 900b5ba..70c26c9 100644 --- a/server/libexec/views/api/show.xml.haml +++ b/server/libexec/views/api/show.xml.haml @@ -1,3 +1,5 @@ %api{ :version=>@version, :driver=>DRIVER } - for entry_point in entry_points %link{ :rel=>entry_point[0], :href=>entry_point[1] } + - for feature in driver.features(entry_point[0]) + %feature{ :name=>feature.name }
From: David Lutterkort lutter@redhat.com
--- server/libexec/lib/sinatra/rabbit.rb | 21 +++++++++++++++++++-- server/libexec/views/docs/collection.html.haml | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/server/libexec/lib/sinatra/rabbit.rb b/server/libexec/lib/sinatra/rabbit.rb index afaa865..7091937 100644 --- a/server/libexec/lib/sinatra/rabbit.rb +++ b/server/libexec/lib/sinatra/rabbit.rb @@ -130,9 +130,9 @@ module Sinatra end
def generate_documentation - coll, oper = self, @operations + coll, oper, features = self, @operations, driver.features(name) ::Sinatra::Application.get("/api/docs/#{@name}") do - @collection, @operations = coll, oper + @collection, @operations, @features = coll, oper, features respond_to do |format| format.html { haml :'docs/collection' } format.xml { haml :'docs/collection' } @@ -174,6 +174,22 @@ module Sinatra end end end + + def add_feature_params(features) + features.each do |f| + f.operations.each do |fop| + if cop = operations[fop.name] + fop.params.each_key do |k| + if cop.params.has_key?(k) + raise DuplicateParamException, "Parameter '#{k}' for operation #{fop.name} defined by collection #{@name} and by feature #{f.name}" + else + cop.params[k] = fop.params[k] + end + end + end + end + end + end end
def collections @@ -188,6 +204,7 @@ module Sinatra def collection(name, &block) raise DuplicateCollectionException if collections[name] collections[name] = Collection.new(name, &block) + collections[name].add_feature_params(driver.features(name)) collections[name].generate end
diff --git a/server/libexec/views/docs/collection.html.haml b/server/libexec/views/docs/collection.html.haml index 7bd5bd6..97b735f 100644 --- a/server/libexec/views/docs/collection.html.haml +++ b/server/libexec/views/docs/collection.html.haml @@ -18,3 +18,20 @@ %td{:style => "width:15em"} %a{:href => "/api/docs/#{@collection.name.to_s}/#{operation}"} #{operation} %td{:style => "width:10em"} #{@operations[operation].description} + +%h3 Features: + +%table + %thead + %tr + %th Name + %th Description + %th Modified Operations + %tbody + - @features.sort_by { |f| f.name.to_s }.each do |feature| + %tr + %td= feature.name + %td= feature.description + %td + - feature.operations.each do |op| + %a{:href => "/api/docs/#{@collection.name.to_s}/#{op.name}"} #{op.name}
From: David Lutterkort lutter@redhat.com
--- .../libexec/lib/deltacloud/base_driver/features.rb | 7 +++++++ .../lib/deltacloud/drivers/mock/mock_driver.rb | 2 ++ .../drivers/rackspace/rackspace_driver.rb | 2 ++ .../lib/deltacloud/drivers/rhevm/rhevm_driver.rb | 2 ++ .../deltacloud/drivers/rimu/rimu_hosting_driver.rb | 3 +++ server/libexec/server.rb | 2 -- 6 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/server/libexec/lib/deltacloud/base_driver/features.rb b/server/libexec/lib/deltacloud/base_driver/features.rb index e626d91..ce88ddc 100644 --- a/server/libexec/lib/deltacloud/base_driver/features.rb +++ b/server/libexec/lib/deltacloud/base_driver/features.rb @@ -110,5 +110,12 @@ module Deltacloud # # Declaration of optional features # + declare_feature :instances, :user_name do + description "Accept a user-defined name on instance creation" + operation :create do + param :name, :string, :optional, nil, + "The user-defined name" + end + end end end diff --git a/server/libexec/lib/deltacloud/drivers/mock/mock_driver.rb b/server/libexec/lib/deltacloud/drivers/mock/mock_driver.rb index 82671aa..c3e2b84 100644 --- a/server/libexec/lib/deltacloud/drivers/mock/mock_driver.rb +++ b/server/libexec/lib/deltacloud/drivers/mock/mock_driver.rb @@ -87,6 +87,8 @@ class MockDriver < Deltacloud::BaseDriver stopped.to( :finish ) .on( :destroy ) end
+ feature :instances, :user_name + def initialize if ENV["DELTACLOUD_MOCK_STORAGE"] @storage_root = ENV["DELTACLOUD_MOCK_STORAGE"] diff --git a/server/libexec/lib/deltacloud/drivers/rackspace/rackspace_driver.rb b/server/libexec/lib/deltacloud/drivers/rackspace/rackspace_driver.rb index d1aa03a..051d44a 100644 --- a/server/libexec/lib/deltacloud/drivers/rackspace/rackspace_driver.rb +++ b/server/libexec/lib/deltacloud/drivers/rackspace/rackspace_driver.rb @@ -24,6 +24,8 @@ module Deltacloud
class RackspaceDriver < Deltacloud::BaseDriver
+ feature :instances, :user_name + def flavors(credentials, opts=nil) racks = new_client( credentials ) results = racks.list_flavors.map do |flav| diff --git a/server/libexec/lib/deltacloud/drivers/rhevm/rhevm_driver.rb b/server/libexec/lib/deltacloud/drivers/rhevm/rhevm_driver.rb index a9fbcc4..32c4f90 100644 --- a/server/libexec/lib/deltacloud/drivers/rhevm/rhevm_driver.rb +++ b/server/libexec/lib/deltacloud/drivers/rhevm/rhevm_driver.rb @@ -32,6 +32,8 @@ class RHEVMDriver < Deltacloud::BaseDriver POWERSHELL="c:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" NO_OWNER=""
+ feature :instances, :user_name + # # Execute a Powershell command, and convert the output # to YAML in order to get back an array of maps. diff --git a/server/libexec/lib/deltacloud/drivers/rimu/rimu_hosting_driver.rb b/server/libexec/lib/deltacloud/drivers/rimu/rimu_hosting_driver.rb index ad1eb04..33b5ea7 100755 --- a/server/libexec/lib/deltacloud/drivers/rimu/rimu_hosting_driver.rb +++ b/server/libexec/lib/deltacloud/drivers/rimu/rimu_hosting_driver.rb @@ -24,6 +24,9 @@ module Deltacloud module Rimu
class RimuHostingDriver < Deltacloud::BaseDriver + + feature :instances, :user_name + def images(credentails, opts=nil) rh = RimuHostingClient.new(credentails) images = rh.list_images.map do | image | diff --git a/server/libexec/server.rb b/server/libexec/server.rb index c730c71..535847a 100644 --- a/server/libexec/server.rb +++ b/server/libexec/server.rb @@ -231,8 +231,6 @@ collection :instances do param :image_id, :string, :required param :realm_id, :string, :optional param :flavor_id, :string, :optional - # FIXME: name is really a driver-specific feature - param :name, :string, :optional control do @image = driver.image(credentials, :id => params[:image_id]) instance = driver.create_instance(credentials, @image.id, params)
From: David Lutterkort lutter@redhat.com
This feature indicates EC2-type data injection --- .../libexec/lib/deltacloud/base_driver/features.rb | 9 +++++++++ .../lib/deltacloud/drivers/ec2/ec2_driver.rb | 4 +++- 2 files changed, 12 insertions(+), 1 deletions(-)
diff --git a/server/libexec/lib/deltacloud/base_driver/features.rb b/server/libexec/lib/deltacloud/base_driver/features.rb index ce88ddc..c6c9126 100644 --- a/server/libexec/lib/deltacloud/base_driver/features.rb +++ b/server/libexec/lib/deltacloud/base_driver/features.rb @@ -117,5 +117,14 @@ module Deltacloud "The user-defined name" end end + + declare_feature :instances, :user_data do + description "Make user-defined data available on a special webserver" + operation :create do + param :user_data, :string, :optional, nil, + "Base64 encoded user data will be published to internal webserver" + end + end + end end diff --git a/server/libexec/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/libexec/lib/deltacloud/drivers/ec2/ec2_driver.rb index dc280fa..edeed09 100644 --- a/server/libexec/lib/deltacloud/drivers/ec2/ec2_driver.rb +++ b/server/libexec/lib/deltacloud/drivers/ec2/ec2_driver.rb @@ -60,6 +60,8 @@ class EC2Driver < Deltacloud::BaseDriver } ), ]
+ feature :instances, :user_data + define_hardware_profile('m1-small') do cpu 1 memory 1.7 @@ -199,7 +201,7 @@ class EC2Driver < Deltacloud::BaseDriver 1,1, [], nil, - '', + params[:user_data], 'public', flavor_id, nil,
From: David Lutterkort lutter@redhat.com
Add method feature? to query if driver supports a certain feature, and discover them during discovery of entry points --- client/lib/deltacloud.rb | 16 ++++++++++++++-- 1 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb index 32e2a0c..b5c2171 100644 --- a/client/lib/deltacloud.rb +++ b/client/lib/deltacloud.rb @@ -42,6 +42,7 @@ class DeltaCloud attr_reader :api_uri attr_reader :entry_points attr_reader :driver_name + attr_reader :features
def self.driver_name(url) DeltaCloud.new( nil, nil, url) do |client| @@ -56,6 +57,7 @@ class DeltaCloud @api_uri = URI.parse( api_uri ) @entry_points = {} @verbose = opts[:verbose] + @features = {} discover_entry_points connect( &block ) self @@ -81,6 +83,10 @@ class DeltaCloud @api_uri.path end
+ def feature?(collection, name) + @features.has_key?(collection) && @features[collection].include?(name) + end + def flavors(opts={}) flavors = [] request(entry_points[:flavors], :get, opts) do |response| @@ -351,15 +357,21 @@ class DeltaCloud attr_reader :http
def discover_entry_points + return if @discovered request(api_uri.to_s) do |response| doc = REXML::Document.new( response.body ) @driver_name = doc.root.attributes['driver'] doc.get_elements( 'api/link' ).each do |link| - rel = link.attributes['rel'] + rel = link.attributes['rel'].to_sym uri = link.attributes['href'] - @entry_points[rel.to_sym] = uri + @entry_points[rel] = uri + @features[rel] ||= [] + link.get_elements('feature').each do |feature| + @features[rel] << feature.attributes['name'].to_sym + end end end + @discovered = true end
def request(path='', method=:get, query_args={}, form_data={}, &block)
From: David Lutterkort lutter@redhat.com
Complain if user assigns name to instance and driver does not support it --- client/bin/deltacloudc | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/client/bin/deltacloudc b/client/bin/deltacloudc index f0b6958..b5f985d 100755 --- a/client/bin/deltacloudc +++ b/client/bin/deltacloudc @@ -130,6 +130,10 @@ if options[:collection] and options[:operation] # --image-id, --flavor-id and --name parameters are used # Returns created instance in plain form if options[:collection].eql?('instances') and options[:operation].eql?('create') + invalid_usage("Missing image-id") unless options[:image_id] + if options[:name] and ! client.feature?(:instances, :user_name) + invalid_usage("Driver does not support user-supplied name") + end params.merge!(:name => options[:name]) if options[:name] params.merge!(:image_id => options[:image_id]) if options[:image_id] params.merge!(:flavor_id => options[:flavor_id]) if options[:flavor_id]
deltacloud-devel@lists.fedorahosted.org