From: Michal Fojtik <[email protected]>
Signed-off-by: Michal fojtik <[email protected]> --- server/bin/deltacloudd | 4 +- server/config.ru | 4 + server/lib/ec2/helpers.rb | 29 ++++++++ server/lib/ec2/helpers/converter.rb | 140 +++++++++++++++++++++++++++++++++++ server/lib/ec2/helpers/errors.rb | 46 ++++++++++++ server/lib/ec2/query_parser.rb | 129 ++++++++++++++++++++++++++++++++ server/lib/ec2/server.rb | 59 +++++++++++++++ 7 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 server/lib/ec2/helpers.rb create mode 100644 server/lib/ec2/helpers/converter.rb create mode 100644 server/lib/ec2/helpers/errors.rb create mode 100644 server/lib/ec2/query_parser.rb create mode 100644 server/lib/ec2/server.rb diff --git a/server/bin/deltacloudd b/server/bin/deltacloudd index 57b6c49..8eb448d 100755 --- a/server/bin/deltacloudd +++ b/server/bin/deltacloudd @@ -61,8 +61,8 @@ BANNER opts.on( '-P', '--provider PROVIDER', 'Use PROVIDER (default is set in the driver)') do |provider| ENV['API_PROVIDER'] = provider end - opts.on('--cimi', 'USe the DMTF CIMI frontend, not the Deltacloud frontend') do - ENV['API_FRONTEND'] = 'cimi' + opts.on('-f', '--frontend FRONTEND', 'Use the different frontend, not the Deltacloud (cimi or ec2)') do |frontend| + ENV['API_FRONTEND'] = frontend end opts.on( '-c', '--config [FILE]', 'Read provider and other config from FILE (default: ~/.deltacloud/config)') do |config| options[:config] = File::expand_path(config || DEFAULT_CONFIG) diff --git a/server/config.ru b/server/config.ru index 2280d0e..5deff47 100644 --- a/server/config.ru +++ b/server/config.ru @@ -30,6 +30,9 @@ if ENV['API_FRONTEND'] == 'deltacloud' elsif ENV['API_FRONTEND'] == 'cimi' API_VERSION = "1.0.0" API_ROOT_URL = "/cloudEntryPoint" +elsif ENV['API_FRONTEND'] == 'ec2' + API_VERSION = "0.1" + API_ROOT_URL = "/" end require File.join(File.dirname(__FILE__), 'lib', ENV['API_FRONTEND'], 'server.rb') @@ -37,6 +40,7 @@ require File.join(File.dirname(__FILE__), 'lib', ENV['API_FRONTEND'], 'server.rb frontend_klass = case ENV['API_FRONTEND'] when 'deltacloud' then Deltacloud::API when 'cimi' then CIMI::API + when 'ec2' then Deltacloud::EC2::API end class IndexEntrypoint < Sinatra::Base diff --git a/server/lib/ec2/helpers.rb b/server/lib/ec2/helpers.rb new file mode 100644 index 0000000..32380fa --- /dev/null +++ b/server/lib/ec2/helpers.rb @@ -0,0 +1,29 @@ +# 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 + module EC2; end +end + +require_relative './helpers/errors' +require_relative './helpers/convertor' + +require_relative '../deltacloud/helpers/driver_helper' +require_relative '../deltacloud/helpers/auth_helper' +require_relative '../deltacloud/core_ext/string' +require_relative '../deltacloud/core_ext/array' +require_relative '../deltacloud/core_ext/hash' +require_relative '../deltacloud/core_ext/integer' +require_relative '../deltacloud/core_ext/proc' diff --git a/server/lib/ec2/helpers/converter.rb b/server/lib/ec2/helpers/converter.rb new file mode 100644 index 0000000..8a70fe1 --- /dev/null +++ b/server/lib/ec2/helpers/converter.rb @@ -0,0 +1,140 @@ +# 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::EC2 + + class Converter + + def self.convert(builder, action, result) + klass_name = ActionHandler::MAPPINGS[action][:method].to_s.camelize + klass = Converter.const_get(klass_name) + klass.new(builder, result).to_xml + end + + class Base + + attr_reader :xml + attr_reader :obj + + def initialize(builder, object) + @xml = builder + @obj = object + end + + end + + class Realms < Base + + def to_xml + xml.availabilityZoneInfo { + obj.each do |item| + xml.item { + xml.zoneName item.id + xml.zoneState item.state + xml.regionName item.name + } + end + } + end + + end + + class Images < Base + + def to_xml + xml.imagesSet { + obj.each do |item| + xml.item { + xml.imageId item.id + xml.imageState item.state.downcase + xml.imageOwnerId item.owner_id + xml.architecture item.architecture + xml.imageType 'machine' + xml.name item.name + xml.description item.description + } + end + } + end + + end + + class CreateInstance < Base + + def to_xml + xml.reservationId 'r-11111111' + xml.ownerId @obj.owner_id + xml.groupSet { + xml.item { + xml.groupId 'sg-11111111' + xml.groupName 'default' + } + } + Instances.new(@xml, [@obj]).instance_set + end + + end + + class Instances < Base + + def instance_set + xml.instancesSet { + obj.each do |item| + xml.item { + xml.instanceId item.id + xml.imageId item.image_id + xml.instanceType item.instance_profile.name + xml.launchTime item.launch_time + xml.ipAddress item.public_addresses.first.address + xml.privateIpAddress item.public_addresses.first.address + xml.dnsName item.public_addresses.first.address + xml.privateDnsName item.private_addresses.first.address + xml.architecture item.instance_profile.architecture + xml.keyName item.keyname + xml.instanceState { + xml.code '16' + xml.name item.state.downcase + } + xml.placement { + xml.availabilityZone item.realm_id + xml.groupName + xml.tenancy 'default' + } + } + end + } + end + + def to_xml + xml.reservationSet { + xml.item { + xml.reservationId 'r-11111111' + xml.ownerId 'deltacloud' + xml.groupSet { + xml.item { + xml.groupId 'sg-11111111' + xml.groupName 'default' + } + } + self.instance_set + } + } + end + + end + + end + +end diff --git a/server/lib/ec2/helpers/errors.rb b/server/lib/ec2/helpers/errors.rb new file mode 100644 index 0000000..7f8d610 --- /dev/null +++ b/server/lib/ec2/helpers/errors.rb @@ -0,0 +1,46 @@ +# 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::EC2 + module Errors + + def report_error(code) + error = (request.env['sinatra.error'] || @exception) + code = 500 if not code and not error.class.method_defined? :code + Nokogiri::XML::Builder.new do |xml| + xml.send(:Response) { + xml.send(:Errors) { + xml.send(:Code, error_for_code(code)) + xml.send(:Message, error.respond_to?(:message) ? error.message : '') + } + xml.send(:RequestID, request_id) + } + end.to_xml + end + + def request_id + Digest::MD5.hexdigest("#{request.env['REMOTE_ADDR']}#{request.env['HTTP_USER_AGENT']}#{Time.now.to_i}#{rand(250)}") + end + + def error_for_code(code) + case code + when 401 then 'AuthFailure' + when 500 then 'InternalError' + else "Unavailable (#{code})" + end + end + + end +end diff --git a/server/lib/ec2/query_parser.rb b/server/lib/ec2/query_parser.rb new file mode 100644 index 0000000..7eebc9a --- /dev/null +++ b/server/lib/ec2/query_parser.rb @@ -0,0 +1,129 @@ +# 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::EC2 + + class ActionHandler + + MAPPINGS = { + :describe_availability_zones => { :method => :realms, :params => { 'ZoneName.1' => :id } }, + :describe_images => { :method => :images, :params => { 'ImageId.1' => :id }}, + :describe_instances => { :method => :instances, :params => {} }, + :run_instances => { :method => :create_instance, :params => { 'ImageId' => :image_id, 'InstanceType' => :hwp_id, 'Placement.AvailabilityZone' => :realm_id }} + } + + attr_reader :action + + def initialize(action) + @action = action + end + + def deltacloud_method + MAPPINGS[action.action][:method] + end + + def deltacloud_method_params + MAPPINGS[action.action][:params].inject({}) do |result, p| + result[p.last] = action.parameters.delete(p.first) + result.delete_if { |k,v| v.nil? } + end + end + + def perform!(credentials, driver) + @result = case deltacloud_method + when :create_instance then driver.send(deltacloud_method, credentials, deltacloud_method_params.delete(:image_id), deltacloud_method_params) + else driver.send(deltacloud_method, credentials, deltacloud_method_params) + end + end + + def to_xml + ResultParser.parse(action, @result).to_xml + end + + end + + class ResultParser + + def self.parse(parser, result) + Nokogiri::XML::Builder.new do |xml| + xml.send(:"#{parser.action.to_s.camelize}Response", :xmlns => 'http://ec2.amazonaws.com/doc/2012-04-01/') { + xml.requestId parser.request_id + new(xml, parser, result).build_xml + } + end + end + + def initialize(xml, parser, result) + @builder = xml + @parser = parser + @result = result + end + + def build_xml + Converter.convert(@builder, @parser.action, @result) + end + + end + + class QueryParser + + def self.parse(params, request_id) + parser = new(request_id, params) + unless parser.valid_action? + raise 'Invalid action (%s)' % parser.action + else + ActionHandler.new(parser) + end + end + + attr_reader :action + attr_reader :parameters + attr_reader :version + attr_reader :expiration + attr_reader :authentication + attr_reader :request_id + + def initialize(request_id, params={}) + @request_id = request_id + @action = (params.delete('Action') || 'Unknown').underscore.intern + @version = params.delete('Version') + @authentication = { + :security_token => params.delete('SecurityToken'), + :access_key_id => params.delete('AWSAccessKeyId'), + :signature => { + :version => params.delete('SignatureVersion'), + :value => params.delete('Signature'), + :method => params.delete('SignatureMethod'), + :timestamp => params.delete('Timestamp') + } + } + @expiration = params.delete('Expires') + @parameters = params + end + + def valid_actions + ActionHandler::MAPPINGS.keys + end + + def valid_action? + return false if @action == :unknown + return false unless valid_actions.include?(@action) + true + end + + + end + +end diff --git a/server/lib/ec2/server.rb b/server/lib/ec2/server.rb new file mode 100644 index 0000000..6586f16 --- /dev/null +++ b/server/lib/ec2/server.rb @@ -0,0 +1,59 @@ +# 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 'rubygems' +require 'nokogiri' +require 'sinatra/base' + +require_relative '../sinatra' +require_relative './helpers' +require_relative './query_parser' +require_relative '../deltacloud/models' +require_relative '../deltacloud/drivers' + +module Deltacloud::EC2 + class API < Sinatra::Base + + extend Deltacloud::Helpers::Drivers + + use Rack::ETag + use Rack::CommonLogger + + helpers Sinatra::AuthHelper + helpers Deltacloud::Helpers::Drivers + helpers Deltacloud::EC2::Errors + + enable :xhtml + enable :dump_errors + enable :show_errors + enable :method_override + disable :show_exceptions + + set :version, API_VERSION + set :root_url, API_ROOT_URL + set :root, File.join(File.dirname(__FILE__), '..', '..') + + after do + headers 'Server' => 'Apache-Deltacloud-EC2/' + settings.version + end + + get '/', :provides => :xml do + ec2_action = QueryParser.parse(params, request_id) + ec2_action.perform!(credentials, driver) + ec2_action.to_xml + end + + end +end -- 1.7.10.1
