diff --git a/install.sh b/install.sh index ca951588b5..55726b6566 100755 --- a/install.sh +++ b/install.sh @@ -1011,6 +1011,7 @@ CLOUD_AUTH_LIB_FILES="src/cloud/common/CloudAuth/OCCICloudAuth.rb \ ECO_LIB_FILES="src/cloud/ec2/lib/EC2QueryClient.rb \ src/cloud/ec2/lib/EC2QueryServer.rb \ src/cloud/ec2/lib/ImageEC2.rb \ + src/cloud/ec2/lib/elastic_ip.rb \ src/cloud/ec2/lib/econe-server.rb" ECO_LIB_CLIENT_FILES="src/cloud/ec2/lib/EC2QueryClient.rb" @@ -1019,6 +1020,11 @@ ECO_LIB_VIEW_FILES="src/cloud/ec2/lib/views/describe_images.erb \ src/cloud/ec2/lib/views/describe_instances.erb \ src/cloud/ec2/lib/views/register_image.erb \ src/cloud/ec2/lib/views/run_instances.erb \ + src/cloud/ec2/lib/views/allocate_address.erb \ + src/cloud/ec2/lib/views/associate_address.erb \ + src/cloud/ec2/lib/views/disassociate_address.erb \ + src/cloud/ec2/lib/views/describe_addresses.erb \ + src/cloud/ec2/lib/views/release_address.erb \ src/cloud/ec2/lib/views/terminate_instances.erb" ECO_BIN_FILES="src/cloud/ec2/bin/econe-server \ diff --git a/src/cloud/ec2/etc/econe.conf b/src/cloud/ec2/etc/econe.conf index 02e352aa1b..0e6c5a0b5d 100644 --- a/src/cloud/ec2/etc/econe.conf +++ b/src/cloud/ec2/etc/econe.conf @@ -37,9 +37,20 @@ # 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG :debug_level: 3 +# +# +# :cluster_id: :datastore_id: +# +# +# +:elasticips_vnet_id: 0 + +:associate_script: /usr/bin/false +:disassociate_script: /usr/bin/false + # VM types allowed and its template file (inside templates directory) :instance_types: :m1.small: diff --git a/src/cloud/ec2/lib/EC2QueryServer.rb b/src/cloud/ec2/lib/EC2QueryServer.rb index 9b02650a9c..dc9234d5c8 100644 --- a/src/cloud/ec2/lib/EC2QueryServer.rb +++ b/src/cloud/ec2/lib/EC2QueryServer.rb @@ -61,10 +61,18 @@ class EC2QueryServer < CloudServer ########################################################################### - def initialize(client, config, logger) + def initialize(client, oneadmin_client, config, logger) super(config, logger) @client = client + @oneadmin_client = oneadmin_client + + if @config[:elasticips_vnet_id].nil? + logger.error { 'ElasticIP module not loaded' } + else + require 'elastic_ip' + extend ElasticIP + end end ########################################################################### @@ -207,9 +215,34 @@ class EC2QueryServer < CloudServer return response.result(binding), 200 end + ########################################################################### + # Elastic IP + ########################################################################### + def allocate_address(params) + return OpenNebula::Error.new('Unsupported'),400 + end + + def release_address(params) + return OpenNebula::Error.new('Unsupported'),400 + end + + def describe_addresses(params) + return OpenNebula::Error.new('Unsupported'),400 + end + + def associate_address(params) + return OpenNebula::Error.new('Unsupported'),400 + end + + def disassociate_address(params) + return OpenNebula::Error.new('Unsupported'),400 + end + ########################################################################### # Helper functions ########################################################################### + private + def render_state(vm) one_state = ONE_STATES[vm.status] ec2_state = EC2_STATES[one_state||:pending] diff --git a/src/cloud/ec2/lib/econe-server.rb b/src/cloud/ec2/lib/econe-server.rb index bcff7bda0b..24601cefc9 100644 --- a/src/cloud/ec2/lib/econe-server.rb +++ b/src/cloud/ec2/lib/econe-server.rb @@ -135,8 +135,9 @@ before do if username.nil? error 401, error_xml("AuthFailure", 0) else - client = settings.cloud_auth.client(username) - @econe_server = EC2QueryServer.new(client, settings.config, settings.logger) + client = settings.cloud_auth.client(username) + oneadmin_client = settings.cloud_auth.client + @econe_server = EC2QueryServer.new(client, oneadmin_client, settings.config, settings.logger) end end @@ -189,6 +190,16 @@ def do_http_request(params) result,rc = @econe_server.describe_instances(params) when 'TerminateInstances' result,rc = @econe_server.terminate_instances(params) + when 'AllocateAddress' + result,rc = @econe_server.allocate_address(params) + when 'AssociateAddress' + result,rc = @econe_server.associate_address(params) + when 'DisassociateAddress' + result,rc = @econe_server.disassociate_address(params) + when 'ReleaseAddress' + result,rc = @econe_server.release_address(params) + when 'DescribeAddresses' + result,rc = @econe_server.describe_addresses(params) end if OpenNebula::is_error?(result) diff --git a/src/cloud/ec2/lib/elastic_ip.rb b/src/cloud/ec2/lib/elastic_ip.rb new file mode 100644 index 0000000000..0a93d34120 --- /dev/null +++ b/src/cloud/ec2/lib/elastic_ip.rb @@ -0,0 +1,198 @@ +module ElasticIP + def allocate_address(params) + # Get public IP + vnet = retrieve_eip_vnet + return vnet, 400 if OpenNebula::is_error?(vnet) + + ips = vnet.retrieve_elements('LEASES/LEASE[USED=0]/IP') + if ips.nil? + logger.error { "There is no lease available to be allocated" } + return OpenNebula::Error.new('AddressLimitExceeded'), 400 + end + + eip = ips.first + + # Hold IP + rc = vnet.hold(eip) + if OpenNebula::is_error?(rc) + logger.error rc.message + return OpenNebula::Error.new('Unsupported'),400 + end + + # Update EC2_ADDRESSES list + xml_hash = {'EC2_ADDRESSES' => {'IP' => eip, "UID" => retrieve_uid}} + vnet.add_element('TEMPLATE', xml_hash) + rc = vnet.update + if OpenNebula::is_error?(rc) + logger.error rc.message + return OpenNebula::Error.new('Unsupported'),400 + end + + response = ERB.new(File.read(@config[:views]+"/allocate_address.erb")) + return response.result(binding), 200 + end + + def release_address(params) + # Check public IP + vnet = retrieve_eip_vnet + return vnet, 400 if OpenNebula::is_error?(vnet) + + eip = params["PublicIp"] + unless vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\" and UID=\"#{retrieve_uid}\"]/IP"] + logger.error { "address:#{eip} does not exist" } + return OpenNebula::Error.new('Unsupported'),400 + end + + # Disassociate address if needed + if vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID"] + cmd_output = `#{@config[:disassociate_script]} #{eip}` + if $?.to_i != 0 + logger.error { cmd_output } + return OpenNebula::Error.new('Unsupported'),400 + end + + vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID") + end + + # Release IP + rc = vnet.release(eip) + if OpenNebula::is_error?(rc) + logger.error {rc.message} + return OpenNebula::Error.new('Unsupported'),400 + end + + # Update EC2_ADDRESSES list + vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]") + rc = vnet.update + if OpenNebula::is_error?(rc) + logger.error {rc.message} + return OpenNebula::Error.new('Unsupported'),400 + end + + response = ERB.new(File.read(@config[:views]+"/release_address.erb")) + return response.result(binding), 200 + end + + def describe_addresses(params) + vnet = retrieve_eip_vnet + return vnet, 400 if OpenNebula::is_error?(vnet) + + erb_version = params['Version'] + user_id = retrieve_uid + + response = ERB.new(File.read(@config[:views]+"/describe_addresses.erb")) + return response.result(binding), 200 + end + + def associate_address(params) + # Check public IP + vnet = retrieve_eip_vnet + return vnet, 400 if OpenNebula::is_error?(vnet) + + user_id = retrieve_uid + eip = params["PublicIp"] + vmid = params['InstanceId'] + vmid = vmid.split('-')[1] if vmid[0]==?i + + unless vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\" and UID=\"#{retrieve_uid}\"]/IP"] + logger.error { "address:#{eip} does not exist" } + return OpenNebula::Error.new('Unsupported'),400 + end + + # Get private IP of the Instance + vm = VirtualMachine.new(VirtualMachine.build_xml(vmid), @client) + rc = vm.info + if OpenNebula::is_error?(rc) + logger.error {rc.message} + return OpenNebula::Error.new('Unsupported'),400 + end + + ips = vm.retrieve_elements('TEMPLATE/NIC/IP') + if ips.nil? + logger.error { "The instance does not have any NIC" } + return OpenNebula::Error.new('Unsupported'),400 + end + + private_ip = ips.first + + # Disassociate address if needed + if vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID"] + cmd_output = `#{@config[:disassociate_script]} #{eip}` + if $?.to_i != 0 + logger.error { cmd_output } + return OpenNebula::Error.new('Unsupported'),400 + end + + vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID") + end + + # Run external script + vnet_base64 = Base64.encode64(vnet.to_xml).delete("\n") + cmd_output = `#{@config[:associate_script]} #{eip} #{private_ip} \"#{vnet_base64}\"` + if $?.to_i != 0 + logger.error { "associate_script" << cmd_output } + return OpenNebula::Error.new('Unsupported'),400 + end + + # Update EC2_ADDRESSES list + vnet.add_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]", "VMID" => vmid) + rc = vnet.update + if OpenNebula::is_error?(rc) + logger.error {rc.message} + return OpenNebula::Error.new('Unsupported'),400 + end + + response = ERB.new(File.read(@config[:views]+"/associate_address.erb")) + return response.result(binding), 200 + end + + def disassociate_address(params) + # Check public IP + vnet = retrieve_eip_vnet + return vnet, 400 if OpenNebula::is_error?(vnet) + + eip = params["PublicIp"] + unless vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\" and UID=\"#{retrieve_uid}\"]/VMID"] + logger.error { "address:#{eip} does not exist or is not associated with any instance" } + return OpenNebula::Error.new('Unsupported'),400 + end + + # Run external script + cmd_output = `#{@config[:disassociate_script]} #{eip}` + if $?.to_i != 0 + logger.error { cmd_output } + return OpenNebula::Error.new('Unsupported'),400 + end + + # Update EC2_ADDRESSES list + vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID") + rc = vnet.update + if OpenNebula::is_error?(rc) + logger.error {rc.message} + return OpenNebula::Error.new('Unsupported'),400 + end + + response = ERB.new(File.read(@config[:views]+"/disassociate_address.erb")) + return response.result(binding), 200 + end + + private + + def retrieve_eip_vnet + vnet = VirtualNetwork.new(VirtualNetwork.build_xml(@config[:elasticips_vnet_id]),@oneadmin_client) + rc = vnet.info + + if OpenNebula::is_error?(rc) + logger.error {rc.message} + return OpenNebula::Error.new('Unsupported') + end + + vnet + end + + def retrieve_uid + user = User.new_with_id(OpenNebula::User::SELF, @client) + user.info + user.id + end +end \ No newline at end of file diff --git a/src/cloud/ec2/lib/views/allocate_address.erb b/src/cloud/ec2/lib/views/allocate_address.erb new file mode 100644 index 0000000000..69f131d427 --- /dev/null +++ b/src/cloud/ec2/lib/views/allocate_address.erb @@ -0,0 +1,8 @@ + + + 4ac62eaf-e266-4058-a970-2c01568cd417 + <%= eip %> + standard + 9090909090 + + diff --git a/src/cloud/ec2/lib/views/associate_address.erb b/src/cloud/ec2/lib/views/associate_address.erb new file mode 100644 index 0000000000..8ce2c3ca9e --- /dev/null +++ b/src/cloud/ec2/lib/views/associate_address.erb @@ -0,0 +1,6 @@ + + + 4ac62eaf-e266-4058-a970-2c01568cd417 + true + + diff --git a/src/cloud/ec2/lib/views/describe_addresses.erb b/src/cloud/ec2/lib/views/describe_addresses.erb new file mode 100644 index 0000000000..3b42278cca --- /dev/null +++ b/src/cloud/ec2/lib/views/describe_addresses.erb @@ -0,0 +1,21 @@ + + + 4ac62eaf-e266-4058-a970-2c01568cd417 + + <% vnet.each("LEASES/LEASE[USED=1 and VID=-1]") do |eip| %> + <% if vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip["IP"]}\"]/UID"] == user_id.to_s %> + + <%= eip["IP"] %> + standard + <% if vm_id = vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip["IP"]}\"]/VMID"] %> + <%= vm_id %> + <% else %> + + <% end %> + + + + <% end %> + <% end %> + + \ No newline at end of file diff --git a/src/cloud/ec2/lib/views/describe_images.erb b/src/cloud/ec2/lib/views/describe_images.erb index 163241c670..fff015f68a 100644 --- a/src/cloud/ec2/lib/views/describe_images.erb +++ b/src/cloud/ec2/lib/views/describe_images.erb @@ -2,7 +2,6 @@ <% impool.each do |im| %> - <% im.info %> ami-<%= sprintf('%08i', im.id) %> <%= im['SOURCE'].split('/').last %> diff --git a/src/cloud/ec2/lib/views/disassociate_address.erb b/src/cloud/ec2/lib/views/disassociate_address.erb new file mode 100644 index 0000000000..491092a84e --- /dev/null +++ b/src/cloud/ec2/lib/views/disassociate_address.erb @@ -0,0 +1,6 @@ + + + 4ac62eaf-e266-4058-a970-2c01568cd417 + true + + diff --git a/src/cloud/ec2/lib/views/release_address.erb b/src/cloud/ec2/lib/views/release_address.erb new file mode 100644 index 0000000000..d7b6e529a4 --- /dev/null +++ b/src/cloud/ec2/lib/views/release_address.erb @@ -0,0 +1,5 @@ + + + 4ac62eaf-e266-4058-a970-2c01568cd417 + true + \ No newline at end of file diff --git a/src/oca/ruby/OpenNebula/Pool.rb b/src/oca/ruby/OpenNebula/Pool.rb index 53bde5ff48..46ddc68652 100644 --- a/src/oca/ruby/OpenNebula/Pool.rb +++ b/src/oca/ruby/OpenNebula/Pool.rb @@ -178,6 +178,8 @@ module OpenNebula def update(xml_method, new_template) return Error.new('ID not defined') if !@pe_id + new_template ||= template_xml.delete("\n").gsub(/\s+/m,'') + rc = @client.call(xml_method,@pe_id, new_template) rc = nil if !OpenNebula.is_error?(rc) diff --git a/src/oca/ruby/OpenNebula/VirtualNetwork.rb b/src/oca/ruby/OpenNebula/VirtualNetwork.rb index 2b82cc1622..332c646720 100644 --- a/src/oca/ruby/OpenNebula/VirtualNetwork.rb +++ b/src/oca/ruby/OpenNebula/VirtualNetwork.rb @@ -90,7 +90,7 @@ module OpenNebula # Replaces the template contents # # +new_template+ New template contents - def update(new_template) + def update(new_template=nil) super(VN_METHODS[:update], new_template) end diff --git a/src/oca/ruby/OpenNebula/XMLUtils.rb b/src/oca/ruby/OpenNebula/XMLUtils.rb index e709b6a8a4..36f62606ca 100644 --- a/src/oca/ruby/OpenNebula/XMLUtils.rb +++ b/src/oca/ruby/OpenNebula/XMLUtils.rb @@ -101,6 +101,44 @@ module OpenNebula end end + def delete_element(xpath) + if NOKOGIRI + @xml.xpath(xpath.to_s).remove + else + @xml.delete_element(xpath.to_s) + end + end + + def add_element(xpath, elems) + elems.each { |key, value| + if value.instance_of?(Hash) + if NOKOGIRI + elem = Nokogiri::XML::Node.new key, @xml.document + value.each { |k2, v2| + child = Nokogiri::XML::Node.new k2, elem + child.content = v2 + elem.add_child(child) + } + @xml.xpath(xpath.to_s).first.add_child(elem) + else + elem = REXML::Element.new(key) + value.each { |k2, v2| + elem.add_element(k2).text = v2 + } + @xml.elements[xpath].add_element(elem) + end + else + if NOKOGIRI + elem = Nokogiri::XML::Node.new key, @xml.document + elem.content = value + @xml.xpath(xpath.to_s).first.add_child(elem) + else + @xml.elements[xpath].add_element(key).text = value + end + end + } + end + # Gets an array of text from elemenets extracted # using the XPATH expression passed as filter def retrieve_elements(filter) @@ -201,6 +239,14 @@ module OpenNebula template_like_str('TEMPLATE', indent) end + def template_xml + if NOKOGIRI + @xml.xpath('TEMPLATE').to_s + else + @xml.elements['TEMPLATE'].to_s + end + end + def template_like_str(root_element, indent=true, xpath_exp=nil) if NOKOGIRI xml_template=@xml.xpath(root_element).to_s