From: Michal Fojtik <[email protected]>
Signed-off-by: Michal fojtik <[email protected]> --- server/lib/deltacloud/hardware_profile.rb | 192 -------------- .../lib/deltacloud/helpers/application_helper.rb | 238 ----------------- server/lib/deltacloud/helpers/assets_helper.rb | 96 +++++++ server/lib/deltacloud/helpers/auth_helper.rb | 73 ++++++ server/lib/deltacloud/helpers/blob_stream.rb | 213 --------------- server/lib/deltacloud/helpers/conversion_helper.rb | 43 --- server/lib/deltacloud/helpers/deltacloud_helper.rb | 273 ++++++++++++++++++++ server/lib/deltacloud/helpers/driver_helper.rb | 57 ++++ .../deltacloud/helpers/hardware_profiles_helper.rb | 50 ---- server/lib/deltacloud/helpers/json_helper.rb | 31 --- server/lib/deltacloud/helpers/rabbit_helper.rb | 34 +++ server/lib/deltacloud/helpers/url_helper.rb | 112 ++++++++ 12 files changed, 645 insertions(+), 767 deletions(-) delete mode 100644 server/lib/deltacloud/hardware_profile.rb delete mode 100644 server/lib/deltacloud/helpers/application_helper.rb create mode 100644 server/lib/deltacloud/helpers/assets_helper.rb create mode 100644 server/lib/deltacloud/helpers/auth_helper.rb delete mode 100644 server/lib/deltacloud/helpers/blob_stream.rb delete mode 100644 server/lib/deltacloud/helpers/conversion_helper.rb create mode 100644 server/lib/deltacloud/helpers/deltacloud_helper.rb create mode 100644 server/lib/deltacloud/helpers/driver_helper.rb delete mode 100644 server/lib/deltacloud/helpers/hardware_profiles_helper.rb delete mode 100644 server/lib/deltacloud/helpers/json_helper.rb create mode 100644 server/lib/deltacloud/helpers/rabbit_helper.rb create mode 100644 server/lib/deltacloud/helpers/url_helper.rb diff --git a/server/lib/deltacloud/hardware_profile.rb b/server/lib/deltacloud/hardware_profile.rb deleted file mode 100644 index 162f7af..0000000 --- a/server/lib/deltacloud/hardware_profile.rb +++ /dev/null @@ -1,192 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -module Deltacloud - class HardwareProfile - - UNITS = { - :memory => "MB", - :storage => "GB", - :architecture => "label", - :cpu => "count" - } - - def self.unit(name) - UNITS[name] - end - - class Property - attr_reader :name, :kind, :default - # kind == :range - attr_reader :first, :last - # kind == :enum - attr_reader :values - # kind == :fixed - attr_reader :value - - def initialize(name, values, opts = {}) - @name = name - if values.is_a?(Range) - @kind = :range - @first = values.first - @last = values.last - @default = values.first - elsif values.is_a?(Array) - @kind = :enum - @values = values - @default = values.first - else - @kind = :fixed - @value = values - @default = @value - end - @default = opts[:default] if opts[:default] - end - - def unit - HardwareProfile.unit(name) - end - - def param - :"hwp_#{name}" - end - - def fixed? - kind == :fixed - end - - def valid?(v) - v = convert_property_value_type(v) - case kind - # NOTE: - # Currently we cannot validate fixed values because of UI - # limitation. In UI we have multiple hwp_* properties which overide - # each other. - # Then provider have one 'static' hardware profile and one - # 'customizable' when user select the static one the UI also send - # values from the customizable one (which will lead to a validation - # error because validation algorith will think that client want to - # overide fixed values. - # - # when :fixed then (v == @default.to_s) - when :fixed then true - when :range then match_type?(first, v) and (first..last).include?(v) - when :enum then match_type?(values.first, v) and values.include?(v) - else false - end - end - - def to_param - Validation::Param.new([param, :string, :optional, []]) - end - - def include?(v) - if kind == :fixed - return v == value - else - return values.include?(v) - end - end - - private - - def match_type?(reference, value) - true if reference.class == value.class - end - - def convert_property_value_type(v) - return v.to_f if v =~ /(\d+)\.(\d+)/ - return v.to_i if v =~ /(\d+)/ - v.to_s - end - end - - class << self - def property(prop) - define_method(prop) do |*args| - values, opts, *ignored = *args - instvar = :"@#{prop}" - unless values.nil? - @properties[prop] = Property.new(prop, values, opts || {}) - end - @properties[prop] - end - end - end - - attr_reader :name - property :cpu - property :architecture - property :memory - property :storage - - def initialize(name,&block) - @properties = {} - @name = name - instance_eval &block if block_given? - end - - def each_property(&block) - @properties.each_value { |prop| yield prop } - end - - def properties - @properties.values - end - - def property(name) - @properties[name.to_sym] - end - - def default?(prop, v) - p = @properties[prop.to_sym] - p && p.default.to_s == v - end - - def to_hash - props = [] - self.each_property do |p| - if p.kind.eql? :fixed - props << { :kind => p.kind, :value => p.value, :name => p.name, :unit => p.unit } - else - param = { :operation => "create", :method => "post", :name => p.name } - if p.kind.eql? :range - param[:range] = { :first => p.first, :last => p.last } - elsif p.kind.eql? :enum - param[:enum] = p.values.collect { |v| { :entry => v } } - end - param - props << { :kind => p.kind, :value => p.default, :name => p.name, :unit => p.unit, :param => param } - end - end - { - :id => self.name, - :properties => props - } - end - - def include?(prop, v) - p = @properties[prop] - p.nil? || p.include?(v) - end - - def params - @properties.values.inject([]) { |m, prop| - m << prop.to_param - }.compact - end - end -end diff --git a/server/lib/deltacloud/helpers/application_helper.rb b/server/lib/deltacloud/helpers/application_helper.rb deleted file mode 100644 index 7a0d58b..0000000 --- a/server/lib/deltacloud/helpers/application_helper.rb +++ /dev/null @@ -1,238 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# Methods added to this helper will be available to all templates in the application. - -require 'benchmark' - -module ApplicationHelper - - include Deltacloud - - def instance_action_method(action) - action_method(action, :instances) - end - - def action_method(action, collection) - collections[collection].operations[action.to_sym].method - end - - def driver_has_feature?(feature_name, collection_name = :instances) - driver.features(collection_name).any? { |f| f.name == feature_name } - end - - def driver_has_auth_features? - driver_has_feature?(:authentication_password) || driver_has_feature?(:authentication_key) - end - - def driver_auth_feature_name - 'key' if driver_has_feature?(:authentication_key) - 'password' if driver_has_feature?(:authentication_password) - end - - def filter_all(model) - filter = {} - filter.merge!(:id => params[:id]) if params[:id] - filter.merge!(:architecture => params[:architecture]) if params[:architecture] - filter.merge!(:owner_id => params[:owner_id]) if params[:owner_id] - filter.merge!(:state => params[:state]) if params[:state] - filter = {} if filter.keys.size.eql?(0) - singular = model.to_s.singularize.to_sym - begin - @benchmark = Benchmark.measure do - @elements = driver.send(model.to_sym, credentials, filter) - end - rescue - @exception = $! - end - if @elements - headers['X-Backend-Runtime'] = @benchmark.real.to_s - instance_variable_set(:"@#{model}", @elements) - respond_to do |format| - format.html { haml :"#{model}/index" } - format.xml { haml :"#{model}/index" } - format.json { convert_to_json(singular, @elements) } - end - else - report_error(@exception.code) - end - end - - def show(model) - @benchmark = Benchmark.measure do - @element = driver.send(model, credentials, { :id => params[:id]} ) - end - headers['X-Backend-Runtime'] = @benchmark.real.to_s - instance_variable_set("@#{model}", @element) - if @element - respond_to do |format| - format.html { haml :"#{model.to_s.pluralize}/show" } - format.xml { haml :"#{model.to_s.pluralize}/show" } - format.json { convert_to_json(model, @element) } - end - else - report_error(404) - end - end - - def report_error(code=nil) - @error, @code = (request.env['sinatra.error'] || @exception), code - @code = 500 if not @code and not @error.class.method_defined? :code - response.status = @code || @error.code - respond_to do |format| - format.xml { haml :"errors/#{@code || @error.code}", :layout => false } - format.json { json_return_error(@error) } - format.html { haml :"errors/#{@code || @error.code}", :layout => :error } - end - end - - def instance_action(name) - original_instance = driver.instance(credentials, :id => params[:id]) - - # If original instance doesn't include called action - # return with 405 error (Method is not Allowed) - unless driver.instance_actions_for(original_instance.state).include?(name.to_sym) - return report_error(405) - end - - @benchmark = Benchmark.measure do - @instance = driver.send(:"#{name}_instance", credentials, params[:id]) - end - - headers['X-Backend-Runtime'] = @benchmark.real.to_s - - if name == :reboot - status 202 - end - - if name == :destroy - respond_to do |format| - format.xml { return 204 } - format.json { return 204 } - format.html { return redirect(instances_url) } - end - end - - if @instance.class != Instance - response['Location'] = instance_url(params[:id]) - halt - end - - respond_to do |format| - format.xml { haml :"instances/show" } - format.html { haml :"instances/show" } - format.json {convert_to_json(:instance, @instance) } - end - end - - def cdata(text = nil, &block) - text ||= capture_haml(&block) - "<![CDATA[#{text.strip}]]>" - end - - def render_cdata(text) - "<![CDATA[#{text.strip}]]>" - end - - def link_to_action(action, url, method) - capture_haml do - haml_tag :form, :method => :post, :action => url, :class => [:link, method], :'data-ajax' => 'false' do - haml_tag :input, :type => :hidden, :name => '_method', :value => method - haml_tag :button, :type => :submit, :'data-ajax' => 'false', :'data-inline' => "true" do - haml_concat action - end - end - end - end - - def link_to_format(format) - return unless request.env['REQUEST_URI'] - uri = request.env['REQUEST_URI'] - return if uri.include?('format=') - uri += uri.include?('?') ? "&format=#{format}" : "?format=#{format}" - capture_haml do - haml_tag :a, :href => uri, :'data-ajax' => 'false', :'data-icon' => 'grid' do - haml_concat format.to_s.upcase - end - end - end - - def image_for_state(state) - state_img = "stopped" if (state!='RUNNING' or state!='PENDING') - capture_haml do - haml_tag :img, :src => "/images/#{state}" % state.downcase, :title => state - end - end - - # Reverse the entrypoints hash for a driver from drivers.yaml; note that - # +d+ is a hash, not an actual driver object - def driver_provider(d) - result = {} - if d[:entrypoints] - d[:entrypoints].each do |kind, details| - details.each do |prov, url| - result[prov] ||= {} - result[prov][kind] = url - end - end - end - result - end - - def header(title, opts={}, &block) - opts[:theme] ||= 'b' - opts[:back] ||= 'true' - capture_haml do - haml_tag :div, :'data-role' => :header, :'data-theme' => opts[:theme], :'data-add-back-btn' => opts[:back] do - haml_tag :a, :'data-rel' => :back do - haml_concat "Back" - end if opts[:back] == 'true' - haml_tag :h1 do - haml_concat title - end - block.call if block_given? - end - end - end - - def subheader(title, opts={}) - opts[:theme] ||= 'a' - capture_haml do - haml_tag :div, :'data-role' => :header, :'data-theme' => opts[:theme] do - haml_tag :p, :class => 'inner-right' do - haml_concat title - end - end - end - end - - def translate_error_code(code) - case code - when 400; { :message => "Bad Request" } - when 401; { :message => "Unauthorized" } - when 403; { :message => "Forbidden" } - when 404; { :message => "Not Found" } - when 405; { :message => "Method Not Allowed" } - when 406; { :message => "Not Acceptable" } - when 500; { :message => "Internal Server Error" } - when 502; { :message => "Backend Server Error" } - when 501; { :message => "Not Supported" } - end - end - - def new_blob_form_url(bucket) - bucket_url(@bucket.name) + "/" + NEW_BLOB_FORM_ID - end -end diff --git a/server/lib/deltacloud/helpers/assets_helper.rb b/server/lib/deltacloud/helpers/assets_helper.rb new file mode 100644 index 0000000..2391da7 --- /dev/null +++ b/server/lib/deltacloud/helpers/assets_helper.rb @@ -0,0 +1,96 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +module Sinatra + module StaticAssets + module Helpers + # In HTML <link> and <img> tags have no end tag. + # In XHTML, on the contrary, these tags must be properly closed. + # + # We can choose the appropriate behaviour with +closed+ option: + # + # image_tag "/images/foo.png", :alt => "Foo itself", :closed => true + # + # The default value of +closed+ option is +false+. + # + def image_tag(source, options = {}) + options[:src] = url_for(source) + tag("img", options) + end + + def stylesheet_link_tag(*sources) + list, options = extract_options(sources) + list.collect { |source| stylesheet_tag(source, options) }.join("\n") + end + + def javascript_script_tag(*sources) + list, options = extract_options(sources) + list.collect { |source| javascript_tag(source, options) }.join("\n") + end + + def link_to(desc, url, options = {}) + tag("a", options.merge(:href => url_for(url))) do + desc + end + end + + private + + def tag(name, local_options = {}) + start_tag = "<#{name}#{tag_options(local_options) if local_options}" + if block_given? + content = yield + "#{start_tag}>#{content}</#{name}>" + else + "#{start_tag}#{"/" if settings.xhtml}>" + end + end + + def tag_options(options) + unless options.empty? + attrs = [] + attrs = options.map { |key, value| %(#{key}="#{Rack::Utils.escape_html(value)}") } + " #{attrs.sort * ' '}" unless attrs.empty? + end + end + + def stylesheet_tag(source, options = {}) + tag("link", { :type => "text/css", + :charset => "utf-8", :media => "screen", :rel => "stylesheet", + :href => url_for(source) }.merge(options)) + end + + def javascript_tag(source, options = {}) + tag("script", { :type => "text/javascript", :charset => "utf-8", + :src => url_for(source) }.merge(options)) do + end + end + + def extract_options(a) + opts = a.last.is_a?(::Hash) ? a.pop : {} + [a, opts] + end + + end + + def self.registered(app) + app.helpers StaticAssets::Helpers + app.disable :xhtml + end + end + + register StaticAssets +end diff --git a/server/lib/deltacloud/helpers/auth_helper.rb b/server/lib/deltacloud/helpers/auth_helper.rb new file mode 100644 index 0000000..eef2521 --- /dev/null +++ b/server/lib/deltacloud/helpers/auth_helper.rb @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# Lazy Basic HTTP authentication. Authentication is only forced when the +# credentials are actually needed. + +module Sinatra + module AuthHelper + class LazyCredentials + def initialize(app) + @app = app + @provided = false + end + + def user + credentials! + @user + end + + def password + credentials! + @password + end + + def provided? + @provided + end + + private + def credentials! + if ENV["API_USER"] && ENV["API_PASSWORD"] + @user = ENV["API_USER"] + @password = ENV["API_PASSWORD"] + @provided = true + end + unless provided? + auth = Rack::Auth::Basic::Request.new(@app.request.env) + @app.authorize! unless auth.provided? && auth.basic? && auth.credentials + @user = auth.credentials[0] + @password = auth.credentials[1] + @provided = true + end + end + + end + + def authorize! + r = "#{Thread.current[:driver]}-deltacloud@#{ENV['HOSTNAME']}" + response['WWW-Authenticate'] = %(Basic realm="#{r}") + throw(:halt, [401, report_error(401)]) + end + + # Request the current user's credentials. Actual credentials are only + # requested when an attempt is made to get the user name or password + def credentials + LazyCredentials.new(self) + end + end + +end diff --git a/server/lib/deltacloud/helpers/blob_stream.rb b/server/lib/deltacloud/helpers/blob_stream.rb deleted file mode 100644 index df5cdc6..0000000 --- a/server/lib/deltacloud/helpers/blob_stream.rb +++ /dev/null @@ -1,213 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -include Deltacloud -begin - require 'eventmachine' - #-- - # based on the example from - # http://macournoyer.com/blog/2009/06/04/pusher-and-async-with-thin/ - #-- - class BlobStream - AsyncResponse = [-1, {}, []].freeze - def self.call(env, credentials, params) - body = DeferrableBody.new - #Get the headers out asap. Don't specify a content-type let - #the client guess and if they can't they SHOULD default to - #'application/octet-stream' anyway as per: - #http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1 - EM.next_tick { env['async.callback'].call [200, { - 'Content-Type' => "#{params['content_type']}", - 'Content-Disposition' => params["content_disposition"], - 'Content-Length' => "#{params['content_length']}"}, body] - } - #call the driver from here. the driver method yields for every chunk - #of blob it receives. Then use body.call to write that chunk as received. - driver.blob_data(credentials, params[:bucket], params[:blob], params) {|chunk| body.call ["#{chunk}"]} #close blob_data block - body.succeed - AsyncResponse # Tell Thin to not close connection & work other requests - end - end - - class DeferrableBody - include EventMachine::Deferrable - - def call(body) - body.each do |chunk| - @body_callback.call(chunk) - end - end - - def each(&blk) - @body_callback = blk - end - end -rescue LoadError => e - # EventMachine isn't available, disable blob streaming - class BlobStream - def self.call(env, credentials, params) - raise NotImplementedError.new("Blob streaming is only supported under Thin") - end - end -end - -module BlobHelper - - def self.extract_blob_metadata_hash(env_hash) - meta_array = env_hash.select{|k,v| k.match(/^HTTP[-_]X[-_]Deltacloud[-_]Blobmeta[-_]/i)} - metadata = meta_array.inject({}){ |result, array| result[array.first.upcase] = array.last; result} - metadata - end - -DELTACLOUD_BLOBMETA_HEADER = /HTTP[-_]X[-_]Deltacloud[-_]Blobmeta[-_]/i - - #e.g. from HTTP-X-Deltacloud-Blobmeta-FOO:BAR to amz-meta-FOO:BAR - def self.rename_metadata_headers(metadata, rename_to) - metadata.gsub_keys(DELTACLOUD_BLOBMETA_HEADER, rename_to) - end - -end - -#Monkey patch for streaming blobs: -# Normally a client will upload a blob to deltacloud and thin will put -# this into a tempfile. Then deltacloud would stream up to the provider: -# i.e. client =-->>TEMP_FILE-->> deltacloud =-->>STREAM-->> provider -# Instead we want to recognise that this is a 'PUT blob' operation and -# start streaming to the provider as the request is received: -# i.e. client =-->>STREAM-->> deltacloud =-->>STREAM-->> provider -module Thin - class Request - - alias_method :move_body_to_tempfile_orig, :move_body_to_tempfile if defined?(Thin::Response) - private - def move_body_to_tempfile - if BlobStreamIO::is_put_blob(self) - @body = BlobStreamIO.new(self) - else - move_body_to_tempfile_orig - end - end - - end -end - -require 'net/http' -require 'net/https' -#monkey patch for Net:HTTP -module Net - class HTTP - - alias :request_orig :request - - def request(req, body = nil, blob_stream = nil, &block) - unless blob_stream - return request_orig(req, body, &block) - end - @blob_req = req - do_start #start the connection - - req.set_body_internal body - begin_transport req - req.write_header_m @socket,@curr_http_version, edit_path(req.path) - @socket - end - - class Put < HTTPRequest - def write_header_m(sock, ver, path) - write_header(sock, ver, path) - end - end - - def end_request - begin - res = HTTPResponse.read_new(@socket) - end while res.kind_of?(HTTPContinue) - res.reading_body(@socket, @blob_req.response_body_permitted?) { - yield res if block_given? } - end_transport @blob_req, res - do_finish - res - end - end - -end - -require 'base64' -class BlobStreamIO - - attr_accessor :size, :provider, :sock - - def initialize(request) - @client_request = request - @size = 0 - bucket, blob = parse_bucket_blob(request.env["PATH_INFO"]) - user, password = parse_credentials(request.env['HTTP_AUTHORIZATION']) - content_type = request.env['CONTENT_TYPE'] || "" - #deal with blob_metadata: (X-Deltacloud-Blobmeta-name: value) - user_meta = BlobHelper::extract_blob_metadata_hash(request.env) - @content_length = request.env['CONTENT_LENGTH'] - @http, provider_request = driver.blob_stream_connection({:user=>user, - :password=>password, :bucket=>bucket, :blob=>blob, :metadata=> user_meta, - :content_type=>content_type, :content_length=>@content_length }) - @content_length = @content_length.to_i #for comparison of size in '<< (data)' - @sock = @http.request(provider_request, nil, true) - end - - def << (data) - @sock.write(data) - @size += data.length - if (@size >= @content_length) - result = @http.end_request - if result.is_a?(Net::HTTPSuccess) - @client_request.env["BLOB_SUCCESS"] = "true" - else - @client_request.env["BLOB_FAIL"] = result.body - end - end - end - - def rewind - end - - #use the Request.env hash (populated by the ThinParser) to determine whether - #this is a post blob operation. By definition, only get here with a body of - # > 112kbytes - thin/lib/thin/request.rb:12 MAX_BODY = 1024 * (80 + 32) - def self.is_put_blob(request = nil) - path = request.env['PATH_INFO'] - method = request.env['REQUEST_METHOD'] - if ( path =~ /^#{Regexp.escape(settings.root_url)}\/buckets/ && method == 'PUT' ) - return true - else - return false - end - end - - private - - def parse_bucket_blob(request_string) - array = request_string.split("/") - blob = array.pop - bucket = array.pop - return bucket, blob - end - - def parse_credentials(request_string) - decoded = Base64.decode64(request_string.split('Basic ').last) - key = decoded.split(':').first - pass = decoded.split(':').last - return key, pass - end - -end diff --git a/server/lib/deltacloud/helpers/conversion_helper.rb b/server/lib/deltacloud/helpers/conversion_helper.rb deleted file mode 100644 index 310ba4b..0000000 --- a/server/lib/deltacloud/helpers/conversion_helper.rb +++ /dev/null @@ -1,43 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -require 'deltacloud/base_driver' - -module ConversionHelper - - def convert_to_json(type, obj) - if ( [ :image, :realm, :instance, :storage_volume, :storage_snapshot, :hardware_profile, :key, :bucket, :blob, :firewall, :load_balancer, :address ].include?( type ) ) - if Array.eql?(obj.class) - data = obj.collect do |o| - o.to_hash.merge({ :href => self.send(:"#{type}_url", type.eql?(:hardware_profile) ? o.name : o.id ) }) - end - type = type.to_s.pluralize - else - data = obj.to_hash - if type == :blob - data.merge!({ :href => self.send(:"bucket_url", "#{data[:bucket]}/#{data[:id]}" ) }) - else - data.merge!({ :href => self.send(:"#{type}_url", data[:id]) }) - if data.has_key?(:hardware_profiles) - data[:hardware_profiles] = data[:hardware_profiles].inject([]){|res, hwp| res << {hwp.name => {:href => self.send(:"hardware_profile_url", hwp.name)}}; res } - end - end - end - return { :"#{type}" => data }.to_json - end - end - -end diff --git a/server/lib/deltacloud/helpers/deltacloud_helper.rb b/server/lib/deltacloud/helpers/deltacloud_helper.rb new file mode 100644 index 0000000..83741f9 --- /dev/null +++ b/server/lib/deltacloud/helpers/deltacloud_helper.rb @@ -0,0 +1,273 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +module Deltacloud::Helpers + module Application + + require 'benchmark' + + def self.included(klass) + klass.class_eval do + set :root_url, API_ROOT_URL + include Sinatra::Rabbit + Sinatra::Rabbit.set :root_path, root_url+'/' + end + end + + def instance_action_method(action) + action_method(action, Sinatra::Rabbit::InstancesCollection) + end + + def action_method(action, collection) + http_method = collection.operation(action).http_method + http_method || Sinatra::Rabbit::BaseCollection.http_method_for(action) + end + + def filter_all(model) + filter = {} + filter.merge!(:id => params[:id]) if params[:id] + filter.merge!(:architecture => params[:architecture]) if params[:architecture] + filter.merge!(:owner_id => params[:owner_id]) if params[:owner_id] + filter.merge!(:state => params[:state]) if params[:state] + filter = {} if filter.keys.size.eql?(0) + singular = model.to_s.singularize.to_sym + begin + @benchmark = Benchmark.measure do + @elements = driver.send(model.to_sym, credentials, filter) + end + rescue + @exception = $! + end + if @elements + headers['X-Backend-Runtime'] = @benchmark.real.to_s + instance_variable_set(:"@#{model}", @elements) + respond_to do |format| + format.html { haml :"#{model}/index" } + format.xml { haml :"#{model}/index" } + format.json { @media_type=:xml; to_json(haml(:"#{model}/index")) } + end + else + report_error(@exception.respond_to?(:code) ? @exception.code : 500) + end + end + + def xml_to_json(model) + @media_type = :xml + to_json(haml(:"#{model}")) + end + + def to_json(xml) + Crack::XML.parse(xml).to_json + end + + def show(model) + @benchmark = Benchmark.measure do + @element = driver.send(model, credentials, { :id => params[:id]} ) + end + headers['X-Backend-Runtime'] = @benchmark.real.to_s + instance_variable_set("@#{model}", @element) + if @element + respond_to do |format| + format.html { haml :"#{model.to_s.pluralize}/show" } + format.xml { haml :"#{model.to_s.pluralize}/show" } + format.json { @media_type=:xml; to_json(haml(:"#{model.to_s.pluralize}/show")) } + end + else + report_error(404) + end + end + + def report_error(code=nil) + @error, @code = (request.env['sinatra.error'] || @exception), code + @code = 500 if not @code and not @error.class.method_defined? :code + response.status = @code || @error.code + respond_to do |format| + format.xml { haml :"errors/#{@code || @error.code}", :layout => false } + format.html { haml :"errors/#{@code || @error.code}", :layout => :error } + end + end + + def instance_action(name) + original_instance = driver.instance(credentials, :id => params[:id]) + + # If original instance doesn't include called action + # return with 405 error (Method is not Allowed) + unless driver.instance_actions_for(original_instance.state).include?(name.to_sym) + return report_error(405) + end + + @benchmark = Benchmark.measure do + @instance = driver.send(:"#{name}_instance", credentials, params[:id]) + end + + headers['X-Backend-Runtime'] = @benchmark.real.to_s + status 202 + + if name == :destroy + respond_to do |format| + format.xml { return 204 } + format.json { return 204 } + format.html { return redirect(instances_url) } + end + end + + if @instance.class != Instance + response['Location'] = instance_url(params[:id]) + halt + end + + respond_to do |format| + format.xml { haml :"instances/show" } + format.html { haml :"instances/show" } + format.json {convert_to_json(:instance, @instance) } + end + end + + def cdata(text = nil, &block) + text ||= capture_haml(&block) + "<![CDATA[#{text.strip}]]>" + end + + def render_cdata(text) + "<![CDATA[#{text.strip}]]>" + end + + def link_to_action(action, url, method) + capture_haml do + haml_tag :form, :method => :post, :action => url, :class => [:link, method], :'data-ajax' => 'false' do + haml_tag :input, :type => :hidden, :name => '_method', :value => method + haml_tag :button, :type => :submit, :'data-ajax' => 'false', :'data-inline' => "true" do + haml_concat action + end + end + end + end + + def link_to_format(format) + return unless request.env['REQUEST_URI'] + uri = request.env['REQUEST_URI'] + return if uri.include?('format=') + uri += uri.include?('?') ? "&format=#{format}" : "?format=#{format}" + capture_haml do + haml_tag :a, :href => uri, :'data-ajax' => 'false', :'data-icon' => 'grid' do + haml_concat format.to_s.upcase + end + end + end + + def image_for_state(state) + state_img = "stopped" if (state!='RUNNING' or state!='PENDING') + capture_haml do + haml_tag :img, :src => "/images/#{state}" % state.downcase, :title => state + end + end + + # Reverse the entrypoints hash for a driver from drivers.yaml; note that + # +d+ is a hash, not an actual driver object + def driver_provider(d) + result = {} + if d[:entrypoints] + d[:entrypoints].each do |kind, details| + details.each do |prov, url| + result[prov] ||= {} + result[prov][kind] = url + end + end + end + result + end + + def header(title, opts={}, &block) + opts[:theme] ||= 'b' + opts[:back] ||= 'true' + capture_haml do + haml_tag :div, :'data-role' => :header, :'data-theme' => opts[:theme], :'data-add-back-btn' => opts[:back] do + haml_tag :a, :'data-rel' => :back do + haml_concat "Back" + end if opts[:back] == 'true' + haml_tag :h1 do + haml_concat title + end + block.call if block_given? + end + end + end + + def subheader(title, opts={}) + opts[:theme] ||= 'a' + capture_haml do + haml_tag :div, :'data-role' => :header, :'data-theme' => opts[:theme] do + haml_tag :p, :class => 'inner-right' do + haml_concat title + end + end + end + end + + def translate_error_code(code) + case code + when 400; { :message => "Bad Request" } + when 401; { :message => "Unauthorized" } + when 403; { :message => "Forbidden" } + when 404; { :message => "Not Found" } + when 405; { :message => "Method Not Allowed" } + when 406; { :message => "Not Acceptable" } + when 500; { :message => "Internal Server Error" } + when 502; { :message => "Backend Server Error" } + when 501; { :message => "Not Supported" } + end + end + + def new_blob_form_url(bucket) + bucket_url(@bucket.name) + "/" + NEW_BLOB_FORM_ID + end + + def format_hardware_property(prop) + return "∅" unless prop + u = hardware_property_unit(prop) + case prop.kind + when :range + "#{prop.first} #{u} - #{prop.last} #{u} (default: #{prop.default} #{u})" + when :enum + prop.values.collect{ |v| "#{v} #{u}"}.join(', ') + " (default: #{prop.default} #{u})" + else + "#{prop.value} #{u}" + end + end + + def format_instance_profile(ip) + o = ip.overrides.collect do |p, v| + u = hardware_property_unit(p) + "#{p} = #{v} #{u}" + end + if o.empty? + "" + else + "with #{o.join(", ")}" + end + end + + private + def hardware_property_unit(prop) + u = ::Deltacloud::HardwareProfile::unit(prop) + u = "" if ["label", "count"].include?(u) + u = "vcpus" if prop == :cpu + u + end + + + + end +end diff --git a/server/lib/deltacloud/helpers/driver_helper.rb b/server/lib/deltacloud/helpers/driver_helper.rb new file mode 100644 index 0000000..18e0598 --- /dev/null +++ b/server/lib/deltacloud/helpers/driver_helper.rb @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +module Deltacloud::Helpers + + module Drivers + + def driver_symbol + driver_name.to_sym + end + + def driver_name + Thread.current[:driver] ||= ENV['API_DRIVER'] + end + + def driver_class_name + driver_name.camelize + end + + def driver_source_name + File.join('..', 'drivers', driver_name, driver_name + '_driver.rb') + end + + def driver_class + begin + m = Deltacloud::Drivers.const_get(driver_class_name) + m.const_get(driver_class_name + "Driver").new + rescue NameError + raise "[ERROR] The driver class name is not defined as #{driver_class_name}Driver" + end + end + + def driver + $:.unshift File.join(File.dirname(__FILE__), '..', '..') + begin + require_relative driver_source_name + driver_class + rescue LoadError + raise "[ERROR] The driver '#{driver_name}' is unknown or not installed" unless valid_driver? + end + end + + end + +end diff --git a/server/lib/deltacloud/helpers/hardware_profiles_helper.rb b/server/lib/deltacloud/helpers/hardware_profiles_helper.rb deleted file mode 100644 index 4da9d8c..0000000 --- a/server/lib/deltacloud/helpers/hardware_profiles_helper.rb +++ /dev/null @@ -1,50 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -module HardwareProfilesHelper - - def format_hardware_property(prop) - return "∅" unless prop - u = hardware_property_unit(prop) - case prop.kind - when :range - "#{prop.first} #{u} - #{prop.last} #{u} (default: #{prop.default} #{u})" - when :enum - prop.values.collect{ |v| "#{v} #{u}"}.join(', ') + " (default: #{prop.default} #{u})" - else - "#{prop.value} #{u}" - end - end - - def format_instance_profile(ip) - o = ip.overrides.collect do |p, v| - u = hardware_property_unit(p) - "#{p} = #{v} #{u}" - end - if o.empty? - "" - else - "with #{o.join(", ")}" - end - end - - private - def hardware_property_unit(prop) - u = ::Deltacloud::HardwareProfile::unit(prop) - u = "" if ["label", "count"].include?(u) - u = "vcpus" if prop == :cpu - u - end -end diff --git a/server/lib/deltacloud/helpers/json_helper.rb b/server/lib/deltacloud/helpers/json_helper.rb deleted file mode 100644 index aea16b6..0000000 --- a/server/lib/deltacloud/helpers/json_helper.rb +++ /dev/null @@ -1,31 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -module JSONHelper - - def json_features_for_entrypoint(entrypoint) - features = driver.features(entrypoint.first).collect { |feature| feature.name } - features.empty? ? {} : { :features => features } - end - - def json_return_error(error) - error_output=Hash.new - error_output[:url] =request.env['REQUEST_URI'] - error_output[:status] =response.status - error_output[:message]=error.message if error - error_output.to_json - end - -end diff --git a/server/lib/deltacloud/helpers/rabbit_helper.rb b/server/lib/deltacloud/helpers/rabbit_helper.rb new file mode 100644 index 0000000..252abe2 --- /dev/null +++ b/server/lib/deltacloud/helpers/rabbit_helper.rb @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +Sinatra::Rabbit::Collection.class_eval do + + def self.standard_index_operation + collection_name = @collection_name + operation :index, :with_capability => collection_name do + control { filter_all collection_name } + end + end + + def self.standard_show_operation + collection_name = @collection_name + operation :show, :with_capability => collection_name do + control { show collection_name.to_s.singularize.intern } + end + end + +end + diff --git a/server/lib/deltacloud/helpers/url_helper.rb b/server/lib/deltacloud/helpers/url_helper.rb new file mode 100644 index 0000000..87bc93e --- /dev/null +++ b/server/lib/deltacloud/helpers/url_helper.rb @@ -0,0 +1,112 @@ +# +# Based on https://github.com/emk/sinatra-url-for/ +# Commit 1df339284203f8f6ed8d +# +# Original license: +# Copyright (C) 2009 Eric Kidd +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the +# following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +# USE OR OTHER DEALINGS IN THE SOFTWARE. + +module Sinatra + module UrlForHelper + + require 'uri' + + def method_missing(name, *args) + if name.to_s =~ /^([\w\_]+)_url$/ + if args.size > 0 + t = $1 + if t =~ /^(stop|reboot|destroy|start|attach|detach)_/ + api_url_for(t.pluralize.split('_').last + '/' + args.first + '/' + $1, :full) + else + api_url_for(t.pluralize, :full) + '/' + "#{args.first}" + end + else + api_url_for($1, :full) + end + else + super + end + end + + def api_url_for(url_fragment, mode=:path_only) + matrix_params = '' + if request.params['api'] + matrix_params += ";provider=%s" % request.params['api']['provider'] if request.params['api']['provider'] + matrix_params += ";driver=%s" % request.params['api']['driver'] if request.params['api']['driver'] + end + url_fragment = "/#{url_fragment}" unless url_fragment =~ /^\// # There is no need to prefix URI with '/' + if mode == :path_only + url_for "#{settings.root_url}#{matrix_params}#{url_fragment}", mode + else + url_for "#{matrix_params}#{url_fragment}", :full + end + end + + # Construct a link to +url_fragment+, which should be given relative to + # the base of this Sinatra app. The mode should be either + # <code>:path_only</code>, which will generate an absolute path within + # the current domain (the default), or <code>:full</code>, which will + # include the site name and port number. (The latter is typically + # necessary for links in RSS feeds.) Example usage: + # + # url_for "/" # Returns "/myapp/" + # url_for "/foo" # Returns "/myapp/foo" + # url_for "/foo", :full # Returns "http://example.com/myapp/foo" + #-- + # See README.rdoc for a list of some of the people who helped me clean + # up earlier versions of this code. + def url_for url_fragment, mode=:path_only + case mode + when :path_only + base = request.script_name + when :full + scheme = request.scheme + port = request.port + request_host = request.host + if request.env['HTTP_X_FORWARDED_FOR'] + scheme = request.env['HTTP_X_FORWARDED_SCHEME'] || scheme + port = request.env['HTTP_X_FORWARDED_PORT'] + request_host = request.env['HTTP_X_FORWARDED_HOST'] + end + if (port.nil? || port == "" || + (scheme == 'http' && port.to_s == '80') || + (scheme == 'https' && port.to_s == '443')) + port = "" + else + port = ":#{port}" + end + base = "#{scheme}://#{request_host}#{port}#{request.script_name}" + else + raise TypeError, "Unknown url_for mode #{mode}" + end + uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI + url_escape = uri_parser.escape(url_fragment) + # Don't add the base fragment if url_for gets called more than once + # per url or the url_fragment passed in is an absolute url + if url_escape.match(/^#{base}/) or url_escape.match(/^http/) + url_escape + else + "#{base}#{url_escape}" + end + end + end + +end -- 1.7.10
