From aaad43a616a550953d63a408424235c1957ff3fb Mon Sep 17 00:00:00 2001 From: KevinLoiseau Date: Sun, 22 Dec 2019 12:17:30 +0100 Subject: [PATCH 1/3] Implement ELBV2 creation pasrer --- .../aws/parsers/elbv2/create_load_balancer.rb | 88 +++++++++++++++++++ .../elbv2/create_load_balancer_tests.rb | 48 ++++++++++ tests/requests/elbv2/helper.rb | 2 +- 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 lib/fog/aws/parsers/elbv2/create_load_balancer.rb create mode 100644 tests/parsers/elbv2/create_load_balancer_tests.rb diff --git a/lib/fog/aws/parsers/elbv2/create_load_balancer.rb b/lib/fog/aws/parsers/elbv2/create_load_balancer.rb new file mode 100644 index 000000000..db5103f3b --- /dev/null +++ b/lib/fog/aws/parsers/elbv2/create_load_balancer.rb @@ -0,0 +1,88 @@ +module Fog + module Parsers + module AWS + module ELBV2 + class CreateLoadBalancer < Fog::Parsers::Base + def reset + reset_load_balancer + reset_availability_zone + @load_balancer_addresses = {} + @state = {} + @results = { 'LoadBalancers' => [] } + @response = { 'CreateLoadBalancerResult' => {}, 'ResponseMetadata' => {} } + end + + def reset_load_balancer + @load_balancer = { 'SecurityGroups' => [], 'AvailabilityZones' => [] } + end + + def reset_availability_zone + @availability_zone = { 'LoadBalancerAddresses' => [] } + end + + def start_element(name, attrs = []) + super + case name + when 'AvailabilityZones' + @in_availability_zones = true + when 'LoadBalancerAddresses' + @in_load_balancer_addresses = true + when 'SecurityGroups' + @in_security_groups = true + when 'State' + @in_state = true + end + end + + def end_element(name) + case name + when 'member' + if @in_availability_zones && @in_load_balancer_addresses + @availability_zone['LoadBalancerAddresses'] << @load_balancer_addresses + elsif @in_availability_zones + @load_balancer['AvailabilityZones'] << @availability_zone + reset_availability_zone + elsif @in_security_groups + @load_balancer['SecurityGroups'] << value + else + @results['LoadBalancers'] << @load_balancer + reset_load_balancer + end + when 'SubnetId', 'ZoneName' + @availability_zone[name] = value + when 'IpAddress', 'AllocationId' + @load_balancer_addresses[name] = value + + when 'CanonicalHostedZoneName', 'CanonicalHostedZoneNameID', 'LoadBalancerName', 'DNSName', 'Scheme', 'Type', + 'LoadBalancerArn', 'IpAddressType', 'CanonicalHostedZoneId', 'VpcId' + @load_balancer[name] = value + when 'CreatedTime' + @load_balancer[name] = Time.parse(value) + + when 'LoadBalancerAddresses' + @in_load_balancer_addresses = false + when 'AvailabilityZones' + @in_availability_zones = false + when 'SecurityGroups' + @in_security_groups = false + when 'State' + @in_state = false + @load_balancer[name] = @state + @state = {} + when 'Code' + @state[name] = value + + when 'RequestId' + @response['ResponseMetadata'][name] = value + + when 'NextMarker' + @results['NextMarker'] = value + when 'CreateLoadBalancerResponse' + @response['CreateLoadBalancerResult'] = @results + end + end + end + end + end + end +end diff --git a/tests/parsers/elbv2/create_load_balancer_tests.rb b/tests/parsers/elbv2/create_load_balancer_tests.rb new file mode 100644 index 000000000..4cbd4bb22 --- /dev/null +++ b/tests/parsers/elbv2/create_load_balancer_tests.rb @@ -0,0 +1,48 @@ +require 'fog/xml' +require 'fog/aws/parsers/elbv2/create_load_balancer' + +CREATE_LOAD_BALANCER_RESULT = <<-EOF + + + + + arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188 + internet-facing + my-load-balancer + vpc-3ac0fb5f + Z2P70J7EXAMPLE + 2016-03-25T21:29:48.850Z + + + subnet-8360a9e7 + us-west-2a + + + subnet-b7d581c0 + us-west-2b + + + + sg-5943793c + + my-load-balancer-424835706.us-west-2.elb.amazonaws.com + + provisioning + + application + + + + + 32d531b2-f2d0-11e5-9192-3fff33344cfa + + +EOF + +Shindo.tests('AWS::ELBV2 | parsers | create_load_balancer', %w[aws elb parser]) do + tests('parses the xml').formats(AWS::ELBV2::Formats::CREATE_LOAD_BALANCER) do + parser = Nokogiri::XML::SAX::Parser.new(Fog::Parsers::AWS::ELBV2::CreateLoadBalancer.new) + parser.parse(CREATE_LOAD_BALANCER_RESULT) + parser.document.response + end +end diff --git a/tests/requests/elbv2/helper.rb b/tests/requests/elbv2/helper.rb index c1d03242c..b86bb2a99 100644 --- a/tests/requests/elbv2/helper.rb +++ b/tests/requests/elbv2/helper.rb @@ -27,7 +27,7 @@ class AWS }) CREATE_LOAD_BALANCER = BASIC.merge({ - 'CreateLoadBalancerResult' => {'LoadBalancers' => [LOAD_BALANCER], 'NextMarker' => Fog::Nullable::String} + 'CreateLoadBalancerResult' => {'LoadBalancers' => [LOAD_BALANCER]} }) LISTENER_DEFAULT_ACTIONS = [{ From ac0826f7e082049923d40e4a561ca6cbeefedd0f Mon Sep 17 00:00:00 2001 From: KevinLoiseau Date: Sun, 22 Dec 2019 18:31:06 +0100 Subject: [PATCH 2/3] Implement ELBV2 creation request --- lib/fog/aws/elbv2.rb | 52 ++++++ .../requests/elbv2/create_load_balancer.rb | 159 ++++++++++++++++++ tests/requests/elbv2/load_balancer_tests.rb | 15 ++ 3 files changed, 226 insertions(+) create mode 100644 lib/fog/aws/requests/elbv2/create_load_balancer.rb create mode 100644 tests/requests/elbv2/load_balancer_tests.rb diff --git a/lib/fog/aws/elbv2.rb b/lib/fog/aws/elbv2.rb index 1363c3c70..1a3885ae5 100644 --- a/lib/fog/aws/elbv2.rb +++ b/lib/fog/aws/elbv2.rb @@ -1,7 +1,11 @@ module Fog module AWS class ELBV2 < ELB + requires :aws_access_key_id, :aws_secret_access_key + recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at, :version, :instrumentor, :instrumentor_name + request_path 'fog/aws/requests/elbv2' + request :create_load_balancer request :describe_load_balancers request :describe_listeners @@ -12,6 +16,54 @@ module Fog super(options) end end + + class Mock + def self.data + @data ||= Hash.new do |hash, region| + owner_id = Fog::AWS::Mock.owner_id + hash[region] = Hash.new do |region_hash, key| + region_hash[key] = { + :owner_id => owner_id, + :load_balancers_v2 => {} + } + end + end + end + + def self.dns_name(name, region) + "#{name}-#{Fog::Mock.random_hex(8)}.#{region}.elb.amazonaws.com" + end + + def self.reset + @data = nil + end + + attr_reader :region + + def initialize(options={}) + @use_iam_profile = options[:use_iam_profile] + + @region = options[:region] || 'us-east-1' + setup_credentials(options) + + Fog::AWS.validate_region!(@region) + end + + def setup_credentials(options) + @aws_access_key_id = options[:aws_access_key_id] + @aws_secret_access_key = options[:aws_secret_access_key] + + @signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key,@region,'elasticloadbalancing') + end + + def data + self.class.data[@region][@aws_access_key_id] + end + + def reset_data + self.class.data[@region].delete(@aws_access_key_id) + end + end end end end diff --git a/lib/fog/aws/requests/elbv2/create_load_balancer.rb b/lib/fog/aws/requests/elbv2/create_load_balancer.rb new file mode 100644 index 000000000..064940953 --- /dev/null +++ b/lib/fog/aws/requests/elbv2/create_load_balancer.rb @@ -0,0 +1,159 @@ +module Fog + module AWS + class ELBV2 + class Real + require 'fog/aws/parsers/elbv2/create_load_balancer' + + # Create a new Elastic Load Balancer + # + # ==== Parameters + # * name<~String> - The name of the load balancer. + # This name must be unique per region per account, can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, + # must not begin or end with a hyphen, and must not begin with "internal-". + # - Required: Yes + # * options<~Hash>: + # * ip_address_type<~String> - [Application Load Balancers] The type of IP addresses used by the subnets for your load balancer. + # The possible values are ipv4 (for IPv4 addresses) and dualstack (for IPv4 and IPv6 addresses). + # Internal load balancers must use ipv4. + # - Required: No + # * scheme<~String> - The default is an Internet-facing load balancer. Valid Values: internet-facing | internal + # - Required: No + # * security_groups<~Array> - The IDs of the security groups for the load balancer. + # - Required: No + # * subnet_mappings<~Array> - The IDs of the public subnets. You can specify only one subnet per Availability Zone. You must specify either subnets or subnet mappings. + # - [Application Load Balancers] You must specify subnets from at least two Availability Zones. + # You cannot specify Elastic IP addresses for your subnets. + # - [Network Load Balancers] You can specify subnets from one or more Availability Zones. + # You can specify one Elastic IP address per subnet if you need static IP addresses for your internet-facing load balancer. + # For internal load balancers, you can specify one private IP address per subnet from the IPv4 range of the subnet. + # - Required: No + # * subnets<~Array> - The IDs of the public subnets. You can specify only one subnet per Availability Zone. You must specify either subnets or subnet mappings. + # - [Application Load Balancers] You must specify subnets from at least two Availability Zones. + # - [Network Load Balancers] You can specify subnets from one or more Availability Zones. + # - Required: No + # * tags<~Hash> - One or more tags to assign to the load balancer. + # - Required: No + # * type<~String> - The type of load balancer. The default is application. Valid Values: application | network + # - Required: No + # ==== Returns + # * response<~Excon::Response>: + # * body<~Hash>: + # * 'ResponseMetadata'<~Hash>: + # * 'RequestId'<~String> - Id of request + # * 'CreateLoadBalancerResult'<~Hash>: + # * 'LoadBalancers'<~Array> + # * 'AvailabilityZones'<~Array>: + # * 'SubnetId'<~String> - ID of the subnet + # * 'ZoneName'<~String> - Name of the Availability Zone + # * 'LoadBalancerAddresses'<~Array>: + # * 'IpAddress'<~String> - IP address + # * 'AllocationId'<~String> - ID of the AWS allocation + # * 'CanonicalHostedZoneName'<~String> - name of the Route 53 hosted zone associated with the load balancer + # * 'CanonicalHostedZoneNameID'<~String> - ID of the Route 53 hosted zone associated with the load balancer + # * 'CreatedTime'<~Time> - time load balancer was created + # * 'DNSName'<~String> - external DNS name of load balancer + # * 'LoadBalancerName'<~String> - name of load balancer + # * 'SecurityGroups'<~Array> - array of security group id + def create_load_balancer(name, options = {}) + params = {} + params.merge!(Fog::AWS.indexed_param('Subnets.member.%d', options[:subnets])) + params.merge!(Fog::AWS.indexed_param('SecurityGroups.member.%d', options[:security_groups])) + params.merge!(Fog::AWS.serialize_keys('Scheme', options[:scheme])) + params.merge!(Fog::AWS.serialize_keys('Type', options[:type])) + params.merge!(Fog::AWS.serialize_keys('IpAddressType', options[:ip_address_type])) + + + unless options[:tags].nil? + tag_keys = options[:tags].keys.sort + tag_values = tag_keys.map { |key| options[:tags][key] } + params.merge!(Fog::AWS.indexed_param('Tags.member.%d.Key', tag_keys)) + params.merge!(Fog::AWS.indexed_param('Tags.member.%d.Value', tag_values)) + end + + unless options[:subnet_mappings].nil? + subnet_ids = [] + allocation_ids = [] + private_ipv4_address = [] + options[:subnet_mappings].each do |subnet_mapping| + subnet_ids.push(subnet_mapping[:subnet_id]) + allocation_ids.push(subnet_mapping[:allocation_id]) + private_ipv4_address.push(subnet_mapping[:private_ipv4_address]) + end + params.merge!(Fog::AWS.indexed_param('SubnetMappings.member.%d.SubnetId', subnet_ids)) + params.merge!(Fog::AWS.indexed_param('SubnetMappings.member.%d.AllocationId', allocation_ids)) + params.merge!(Fog::AWS.indexed_param('SubnetMappings.member.%d.PrivateIPv4Address', private_ipv4_address)) + end + + + request({ + 'Action' => 'CreateLoadBalancer', + 'Name' => name, + :parser => Fog::Parsers::AWS::ELBV2::CreateLoadBalancer.new + }.merge!(params)) + end + end + + class Mock + def create_load_balancer(name, options = {}) + response = Excon::Response.new + response.status = 200 + + raise Fog::AWS::ELBV2::IdentifierTaken if self.data[:load_balancers_v2].key? name + + dns_name = Fog::AWS::ELBV2::Mock.dns_name(name, @region) + type = options[:type] || 'application' + + subnet_ids = options[:subnets] || [] + region = if subnet_ids.any? + # using Hash here for Rubt 1.8.7 support. + Hash[ + Fog::AWS::Compute::Mock.data.select do |_, region_data| + unless region_data[@aws_access_key_id].nil? + region_data[@aws_access_key_id][:subnets].any? do |region_subnets| + subnet_ids.include? region_subnets['subnetId'] + end + end + end + ].keys[0] + else + 'us-east-1' + end + + subnets = Fog::AWS::Compute::Mock.data[region][@aws_access_key_id][:subnets].select {|e| subnet_ids.include?(e["subnetId"]) } + availability_zones = subnets.map do |subnet| + { "LoadBalancerAddresses"=>[], "SubnetId"=>subnet["subnetId"], "ZoneName"=>subnet["availabilityZone"]} + end + vpc_id = subnets.first['vpcId'] + + self.data[:tags] ||= {} + self.data[:tags][name] = options[:tags] || {} + + load_balancer = { + 'AvailabilityZones' => availability_zones || [], + 'Scheme' => options[:scheme] || 'internet-facing', + 'SecurityGroups' => options[:security_groups] || [], + 'CanonicalHostedZoneId' => '', + 'CreatedTime' => Time.now, + 'DNSName' => dns_name, + 'VpcId' => vpc_id, + 'Type' => type, + 'State' => {'Code' => 'provisioning'}, + 'LoadBalancerArn' => Fog::AWS::Mock.arn('elasticloadbalancing', self.data[:owner_id], "loadbalancer/#{type[0..2]}/#{name}/#{Fog::AWS::Mock.key_id}"), + 'LoadBalancerName' => name + } + self.data[:load_balancers_v2][name] = load_balancer + response.body = { + 'ResponseMetadata' => { + 'RequestId' => Fog::AWS::Mock.request_id + }, + 'CreateLoadBalancerResult' => { + 'LoadBalancers' => [load_balancer] + } + } + + response + end + end + end + end +end diff --git a/tests/requests/elbv2/load_balancer_tests.rb b/tests/requests/elbv2/load_balancer_tests.rb new file mode 100644 index 000000000..0c84b5298 --- /dev/null +++ b/tests/requests/elbv2/load_balancer_tests.rb @@ -0,0 +1,15 @@ +Shindo.tests('AWS::ELBV2 | load_balancer_tests', ['aws', 'elb']) do + @load_balancer_id = 'fog-test-elb' + @key_name = 'fog-test' + vpc = Fog::Compute[:aws].create_vpc('10.255.254.64/28').body['vpcSet'].first + @subnet_id = Fog::Compute[:aws].create_subnet(vpc['vpcId'], vpc['cidrBlock']).body['subnet']['subnetId'] + + tests('success') do + tests("#create_load_balancer").formats(AWS::ELBV2::Formats::CREATE_LOAD_BALANCER) do + options = { + subnets: [@subnet_id] + } + Fog::AWS[:elbv2].create_load_balancer(@load_balancer_id, options).body + end + end +end From a1b966d572546970e2a538136e9da49d2113fe51 Mon Sep 17 00:00:00 2001 From: KevinLoiseau Date: Sun, 22 Dec 2019 19:00:00 +0100 Subject: [PATCH 3/3] Add test for ELBV2 describe_load_balancers --- .../requests/elbv2/describe_load_balancers.rb | 48 +++++++++++++++++++ tests/requests/elbv2/load_balancer_tests.rb | 8 ++++ 2 files changed, 56 insertions(+) diff --git a/lib/fog/aws/requests/elbv2/describe_load_balancers.rb b/lib/fog/aws/requests/elbv2/describe_load_balancers.rb index 5120c2a8f..b9106ffbb 100644 --- a/lib/fog/aws/requests/elbv2/describe_load_balancers.rb +++ b/lib/fog/aws/requests/elbv2/describe_load_balancers.rb @@ -47,6 +47,54 @@ module Fog }.merge!(options)) end end + + class Mock + def describe_load_balancers(options = {}) + unless options.is_a?(Hash) + Fog::Logger.deprecation("describe_load_balancers with #{options.class} is deprecated, use all('LoadBalancerNames' => []) instead [light_black](#{caller.first})[/]") + options = { 'LoadBalancerNames' => [options].flatten } + end + + lb_names = options['LoadBalancerNames'] || [] + + lb_names = [*lb_names] + load_balancers = if lb_names.any? + lb_names.map do |lb_name| + lb = self.data[:load_balancers_v2].find { |name, data| name == lb_name } + raise Fog::AWS::ELBV2::NotFound unless lb + lb[1].dup + end.compact + else + self.data[:load_balancers_v2].map { |lb, values| values.dup } + end + + marker = options.fetch('Marker', 0).to_i + if load_balancers.count - marker > 400 + next_marker = marker + 400 + load_balancers = load_balancers[marker...next_marker] + else + next_marker = nil + end + + response = Excon::Response.new + response.status = 200 + + response.body = { + 'ResponseMetadata' => { + 'RequestId' => Fog::AWS::Mock.request_id + }, + 'DescribeLoadBalancersResult' => { + 'LoadBalancers' => load_balancers + } + } + + if next_marker + response.body['DescribeLoadBalancersResult']['NextMarker'] = next_marker.to_s + end + + response + end + end end end end diff --git a/tests/requests/elbv2/load_balancer_tests.rb b/tests/requests/elbv2/load_balancer_tests.rb index 0c84b5298..4230d68e5 100644 --- a/tests/requests/elbv2/load_balancer_tests.rb +++ b/tests/requests/elbv2/load_balancer_tests.rb @@ -11,5 +11,13 @@ Shindo.tests('AWS::ELBV2 | load_balancer_tests', ['aws', 'elb']) do } Fog::AWS[:elbv2].create_load_balancer(@load_balancer_id, options).body end + + tests("#describe_load_balancers").formats(AWS::ELBV2::Formats::DESCRIBE_LOAD_BALANCERS) do + Fog::AWS[:elbv2].describe_load_balancers.body + end + + tests('#describe_load_balancers with bad name') do + raises(Fog::AWS::ELBV2::NotFound) { Fog::AWS[:elbv2].describe_load_balancers('LoadBalancerNames' => 'none-such-lb') } + end end end