diff --git a/fog.gemspec b/fog.gemspec index 1115b233c..c7d03859b 100644 --- a/fog.gemspec +++ b/fog.gemspec @@ -44,10 +44,14 @@ Gem::Specification.new do |s| s.add_dependency("fog-core", "~> 1.21", ">= 1.21.1") s.add_dependency("fog-json") - s.add_dependency('nokogiri', '~> 1.5', '>= 1.5.11') - - # Modular providers - s.add_dependency("fog-brightbox") + s.add_dependency('builder') + s.add_dependency('excon', '~>0.31.0') + s.add_dependency('formatador', '~>0.2.0') + s.add_dependency('ipaddress', '~>0.5') + s.add_dependency('mime-types') + s.add_dependency('net-scp', '~>1.1') + s.add_dependency('net-ssh', '>=2.1.3') + s.add_dependency('nokogiri', '>=1.5.11') ## List your development dependencies here. Development dependencies are ## those that are only needed during development diff --git a/lib/fog/aws/requests/compute/attach_network_interface.rb b/lib/fog/aws/requests/compute/attach_network_interface.rb index 8d63014f7..93912ce47 100644 --- a/lib/fog/aws/requests/compute/attach_network_interface.rb +++ b/lib/fog/aws/requests/compute/attach_network_interface.rb @@ -35,10 +35,17 @@ module Fog def attach_network_interface(nic_id, instance_id, device_index) response = Excon::Response.new - if self.data[:network_interfaces][nic_id] + if ! self.data[:instances].find{ |i,i_conf| + i_conf['instanceId'] == instance_id + } + raise Fog::Compute::AWS::NotFound.new("The instance ID '#{instance_id}' does not exist") + elsif self.data[:network_interfaces].find{ |ni,ni_conf| ni_conf['attachment']['instanceId'] == instance_id && ni_conf['attachment']['deviceIndex'] == device_index } + raise Fog::Compute::AWS::Error.new("InvalidParameterValue => Instance '#{instance_id}' already has an interface attached at device index '#{device_index}'.") + elsif self.data[:network_interfaces][nic_id] attachment = self.data[:network_interfaces][nic_id]['attachment'] attachment['attachmentId'] = Fog::AWS::Mock.request_id attachment['instanceId'] = instance_id + attachment['deviceIndex'] = device_index response.status = 200 response.body = { diff --git a/lib/fog/aws/requests/compute/create_network_interface.rb b/lib/fog/aws/requests/compute/create_network_interface.rb index cc35b90f6..3696a8166 100644 --- a/lib/fog/aws/requests/compute/create_network_interface.rb +++ b/lib/fog/aws/requests/compute/create_network_interface.rb @@ -65,45 +65,63 @@ module Fog def create_network_interface(subnetId, options = {}) response = Excon::Response.new if subnetId - id = Fog::AWS::Mock.network_interface_id + subnet = self.data[:subnets].find{ |s| s['subnetId'] == subnetId } + if subnet.nil? + raise Fog::Compute::AWS::Error.new("Unknown subnet '#{subnetId}' specified") + else + id = Fog::AWS::Mock.network_interface_id + cidr_block = IPAddress.parse(subnet['cidrBlock']) - groups = {} - if options['GroupSet'] - options['GroupSet'].each do |group_id| - name = self.data[:security_groups].select { |k,v| v['groupId'] == group_id }.first - if name.nil? - raise Fog::Compute::AWS::Error.new("Unknown security group '#{group_id}' specified") + groups = {} + if options['GroupSet'] + options['GroupSet'].each do |group_id| + name = self.data[:security_groups].select { |k,v| v['groupId'] == group_id } .first.first + if name.nil? + raise Fog::Compute::AWS::Error.new("Unknown security group '#{group_id}' specified") + end + groups[group_id] = name end - groups[group_id] = name end - end - if options['PrivateIpAddress'].nil? - options['PrivateIpAddress'] = "10.0.0.2" - end - data = { - 'networkInterfaceId' => id, - 'subnetId' => subnetId, - 'vpcId' => 'mock-vpc-id', - 'availabilityZone' => 'mock-zone', - 'description' => options['Description'], - 'ownerId' => '', - 'requesterManaged' => 'false', - 'status' => 'available', - 'macAddress' => '00:11:22:33:44:55', - 'privateIpAddress' => options['PrivateIpAddress'], - 'sourceDestCheck' => true, - 'groupSet' => groups, - 'attachment' => {}, - 'association' => {}, - 'tagSet' => {} - } - self.data[:network_interfaces][id] = data - response.body = { - 'requestId' => Fog::AWS::Mock.request_id, - 'networkInterface' => data - } - response + if options['PrivateIpAddress'].nil? + # Here we try to act like a DHCP server and pick the first + # available IP (not including the first in the cidr block, + # which is typically reserved for the gateway). + cidr_block.each_host do |p_ip| + unless self.data[:network_interfaces].map{ |ni, ni_conf| ni_conf['privateIpAddress'] }.include?p_ip.to_s || + cidr_block.first == p_ip + options['PrivateIpAddress'] = p_ip.to_s + break + end + end + elsif self.data[:network_interfaces].map{ |ni,ni_conf| ni_conf['privateIpAddress'] }.include?options['PrivateIpAddress'] + raise Fog::Compute::AWS::Error.new("InUse => The specified address is already in use.") + end + + data = { + 'networkInterfaceId' => id, + 'subnetId' => subnetId, + 'vpcId' => 'mock-vpc-id', + 'availabilityZone' => 'mock-zone', + 'description' => options['Description'], + 'ownerId' => '', + 'requesterManaged' => 'false', + 'status' => 'available', + 'macAddress' => '00:11:22:33:44:55', + 'privateIpAddress' => options['PrivateIpAddress'], + 'sourceDestCheck' => true, + 'groupSet' => groups, + 'attachment' => {}, + 'association' => {}, + 'tagSet' => {} + } + self.data[:network_interfaces][id] = data + response.body = { + 'requestId' => Fog::AWS::Mock.request_id, + 'networkInterface' => data + } + response + end else response.status = 400 response.body = { diff --git a/lib/fog/aws/requests/compute/create_subnet.rb b/lib/fog/aws/requests/compute/create_subnet.rb index 1e6847f0a..c28cb7928 100644 --- a/lib/fog/aws/requests/compute/create_subnet.rb +++ b/lib/fog/aws/requests/compute/create_subnet.rb @@ -3,6 +3,7 @@ module Fog class AWS class Real + require 'ipaddress' require 'fog/aws/parsers/compute/create_subnet' # Creates a Subnet with the CIDR block you specify. @@ -44,7 +45,20 @@ module Fog def create_subnet(vpcId, cidrBlock, options = {}) av_zone = options['AvailabilityZone'].nil? ? 'us-east-1c' : options['AvailabilityZone'] Excon::Response.new.tap do |response| - if cidrBlock && vpcId + if cidrBlock && vpcId + vpc = self.data[:vpcs].find{ |v| v['vpcId'] == vpcId } + if vpc.nil? + raise Fog::Compute::AWS::NotFound.new("The vpc ID '#{vpcId}' does not exist") + end + if ! ::IPAddress.parse(vpc['cidrBlock']).include?(::IPAddress.parse(cidrBlock)) + raise Fog::Compute::AWS::Error.new("Range => The CIDR '#{cidrBlock}' is invalid.") + end + self.data[:subnets].each do |subnet| + if ::IPAddress.parse(subnet['cidrBlock']).include?(::IPAddress.parse(cidrBlock)) + raise Fog::Compute::AWS::Error.new("Conflict => The CIDR '#{cidrBlock}' conflicts with another subnet") + end + end + response.status = 200 data = { 'subnetId' => Fog::AWS::Mock.subnet_id, diff --git a/lib/fog/aws/requests/compute/describe_instances.rb b/lib/fog/aws/requests/compute/describe_instances.rb index e2cce0142..d05c0d63e 100644 --- a/lib/fog/aws/requests/compute/describe_instances.rb +++ b/lib/fog/aws/requests/compute/describe_instances.rb @@ -184,8 +184,6 @@ module Fog instance['ipAddress'] = Fog::AWS::Mock.ip_address instance['originalIpAddress'] = instance['ipAddress'] instance['dnsName'] = Fog::AWS::Mock.dns_name_for(instance['ipAddress']) - instance['privateIpAddress'] = Fog::AWS::Mock.private_ip_address - instance['privateDnsName'] = Fog::AWS::Mock.private_dns_name_for(instance['privateIpAddress']) instance['instanceState'] = { 'code' => 16, 'name' => 'running' } end when 'rebooting' @@ -209,18 +207,30 @@ module Fog if self.data[:instances][instance['instanceId']] - instance['networkInterfaces'] = self.data[:network_interfaces].select{|ni,ni_conf| - ni_conf['attachment']['instanceId'] == instance['instanceId'] - }.map{|ni,ni_conf| - { - 'ownerId' => ni_conf['ownerId'], - 'subnetId' => ni_conf['subnetId'], - 'vpcId' => ni_conf['vpcId'], - 'networkInterfaceId' => ni_conf['networkInterfaceId'], - 'groupSet' => ni_conf['groupSet'], - 'attachmentId' => ni_conf['attachment']['attachmentId'] - } + nics = self.data[:network_interfaces].select{|ni,ni_conf| + ni_conf['attachment']['instanceId'] == instance['instanceId'] + } + instance['networkInterfaces'] = nics.map{|ni,ni_conf| + { + 'ownerId' => ni_conf['ownerId'], + 'subnetId' => ni_conf['subnetId'], + 'vpcId' => ni_conf['vpcId'], + 'networkInterfaceId' => ni_conf['networkInterfaceId'], + 'groupSet' => ni_conf['groupSet'], + 'attachmentId' => ni_conf['attachment']['attachmentId'] } + } + if nics.count > 0 + + instance['privateIpAddress'] = nics.sort_by {|ni, ni_conf| + ni_conf['attachment']['deviceIndex'] + }.map{ |ni, ni_conf| ni_conf['privateIpAddress'] }.first + + instance['privateDnsName'] = Fog::AWS::Mock.private_dns_name_for(instance['privateIpAddress']) + else + instance['privateIpAddress'] = '' + instance['privateDnsName'] = '' + end reservation_set[instance['reservationId']] ||= { 'groupSet' => instance['groupSet'], diff --git a/lib/fog/aws/requests/compute/run_instances.rb b/lib/fog/aws/requests/compute/run_instances.rb index 9d10717eb..e43cfd49c 100644 --- a/lib/fog/aws/requests/compute/run_instances.rb +++ b/lib/fog/aws/requests/compute/run_instances.rb @@ -184,6 +184,16 @@ module Fog } end + if options['SubnetId'] + if options['PrivateIpAddress'] + ni_options = {'PrivateIpAddress' => options['PrivateIpAddress']} + else + ni_options = {} + end + + network_interface_id = create_network_interface(options['SubnetId'], ni_options).body['networkInterface']['networkInterfaceId'] + end + network_interfaces = (options['NetworkInterfaces'] || []).inject([]) do |mapping, device| device_index = device.fetch("DeviceIndex", 0) subnet_id = device.fetch("SubnetId", options[:subnet_id] || Fog::AWS::Mock.subnet_id) @@ -238,12 +248,15 @@ module Fog 'groupIds' => [], 'groupSet' => group_set, 'iamInstanceProfile' => {}, - 'networkInterfaces' => [], 'ownerId' => self.data[:owner_id], - 'privateIpAddress' => nil, 'reservationId' => reservation_id, 'stateReason' => {} }) + + if options['SubnetId'] + attachment_id = attach_network_interface(network_interface_id, instance_id, 0).data[:body]['attachmentId'] + modify_network_interface_attribute(network_interface_id, 'attachment', {'attachmentId' => attachment_id, 'deleteOnTermination' => 'true'}) + end end response.body = { 'groupSet' => group_set, diff --git a/tests/aws/requests/compute/instance_tests.rb b/tests/aws/requests/compute/instance_tests.rb index 3e2f61bce..2047593aa 100644 --- a/tests/aws/requests/compute/instance_tests.rb +++ b/tests/aws/requests/compute/instance_tests.rb @@ -204,11 +204,15 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do # Test network interface attachment tests('#describe_instances networkInterfaces') do - data = Fog::Compute[:aws].create_network_interface('subnet-12345678').body + vpc = Fog::Compute[:aws].vpcs.create('cidr_block' => '10.0.10.0/16') + subnet = Fog::Compute[:aws].subnets.create('vpc_id' => vpc.id, 'cidr_block' => '10.0.10.0/16') + data = Fog::Compute[:aws].create_network_interface(subnet.subnet_id).body @network_interface_id = data['networkInterface']['networkInterfaceId'] - Fog::Compute[:aws].attach_network_interface(@network_interface_id, @instance_id, 1) + Fog::Compute[:aws].attach_network_interface(@network_interface_id, @instance_id, '1') body = Fog::Compute[:aws].describe_instances('instance-id' => "#{@instance_id}").body tests("returns 1 attachment").returns(1) { body['reservationSet'].first['instancesSet'].first['networkInterfaces'].size } + subnet.destroy + vpc.destroy end another_server.destroy @@ -269,6 +273,9 @@ Shindo.tests('Fog::Compute[:aws] | instance requests', ['aws']) do tests('failure') do + tests("#run_instances(nil, 1, 1, {'SubnetId'=>'subnet-00000000'}").raises(::Fog::Compute::AWS::Error) do + Fog::Compute[:aws].run_instances(nil, 1, 1, {'SubnetId' => 'subnet-000000'}) + end tests("#get_console_output('i-00000000')").raises(Fog::Compute::AWS::NotFound) do Fog::Compute[:aws].get_console_output('i-00000000') end diff --git a/tests/aws/requests/compute/network_interface_tests.rb b/tests/aws/requests/compute/network_interface_tests.rb index f08d7d619..dc4e7273a 100644 --- a/tests/aws/requests/compute/network_interface_tests.rb +++ b/tests/aws/requests/compute/network_interface_tests.rb @@ -120,11 +120,12 @@ Shindo.tests('Fog::Compute[:aws] | network interface requests', ['aws']) do @server = Fog::Compute[:aws].servers.create({:flavor_id => 'm1.small', :subnet_id => @subnet_id }) @server.wait_for { ready? } - @instance_id=@server.id + @instance_id=@server.id - # attach + # attach + @device_index = 1 tests('#attach_network_interface').formats(@attach_network_interface_format) do - data = Fog::Compute[:aws].attach_network_interface(@nic_id, @instance_id, 1).body + data = Fog::Compute[:aws].attach_network_interface(@nic_id, @instance_id, @device_index).body @attachment_id = data['attachmentId'] data end @@ -201,4 +202,48 @@ Shindo.tests('Fog::Compute[:aws] | network interface requests', ['aws']) do @subnet.destroy @vpc.destroy end + + tests('failure') do + + # Attempt to attach a nonexistent interface + tests("#attach_network_interface('eni-00000000', 'i-00000000', '1')").raises(::Fog::Compute::AWS::NotFound) do + Fog::Compute[:aws].attach_network_interface('eni-00000000', 'i-00000000', '1') + end + + # Create environment + @vpc = Fog::Compute[:aws].vpcs.create('cidr_block' => '10.0.10.0/24') + @subnet = Fog::Compute[:aws].subnets.create('vpc_id' => @vpc.id, 'cidr_block' => '10.0.10.16/28') + + @subnet_id = @subnet.subnet_id + + data = Fog::Compute[:aws].create_network_interface(@subnet_id).body + @nic_id = data['networkInterface']['networkInterfaceId'] + + # Attempt to re-use an existing IP for another ENI + tests("#create_network_interface('#{@subnet_id}', {'PrivateIpAddress' => '#{data['networkInterface']['privateIpAddress']}'}").raises(::Fog::Compute::AWS::Error) do + Fog::Compute[:aws].create_network_interface(@subnet_id, {'PrivateIpAddress' => data['networkInterface']['privateIpAddress']}) + end + + # Attempt to attach a valid ENI to a nonexistent instance. + tests("#attach_network_interface('#{@nic_id}', 'i-00000000', '0')").raises(::Fog::Compute::AWS::NotFound) do + Fog::Compute[:aws].attach_network_interface(@nic_id, 'i-00000000', '0') + end + + @server = Fog::Compute[:aws].servers.create({:flavor_id => 'm1.small', :subnet_id => @subnet_id }) + @server.wait_for { ready? } + @instance_id=@server.id + @device_index = 1 + data = Fog::Compute[:aws].attach_network_interface(@nic_id, @instance_id, @device_index).body + + # Attempt to attach two ENIs to the same instance with the same device + # index. + tests("#attach_network_interface('#{@nic_id}', '#{@instance_id}', '#{@device_index}')").raises(::Fog::Compute::AWS::Error) do + Fog::Compute[:aws].attach_network_interface(@nic_id, @instance_id, @device_index) + end + + @server.destroy + @subnet.destroy + @vpc.destroy + + end end diff --git a/tests/aws/requests/compute/subnet_tests.rb b/tests/aws/requests/compute/subnet_tests.rb index de9b0a544..22e91c646 100644 --- a/tests/aws/requests/compute/subnet_tests.rb +++ b/tests/aws/requests/compute/subnet_tests.rb @@ -20,9 +20,10 @@ Shindo.tests('Fog::Compute[:aws] | subnet requests', ['aws']) do 'requestId' => String } + @vpc=Fog::Compute[:aws].vpcs.create('cidr_block' => '10.0.10.0/24') + @vpc_id = @vpc.id + tests('success') do - @vpc=Fog::Compute[:aws].vpcs.create('cidr_block' => '10.0.10.0/24') - @vpc_id = @vpc.id @subnet_id = nil tests('#create_subnet').formats(@single_subnet_format) do @@ -38,6 +39,23 @@ Shindo.tests('Fog::Compute[:aws] | subnet requests', ['aws']) do tests("#delete_subnet('#{@subnet_id}')").formats(AWS::Compute::Formats::BASIC) do Fog::Compute[:aws].delete_subnet(@subnet_id).body end - @vpc.destroy end + + tests('failure') do + tests("#create_subnet('vpc-00000000', '10.0.10.0/16')").raises(Fog::Compute::AWS::NotFound) do + Fog::Compute[:aws].create_subnet('vpc-00000000', '10.0.10.0/16') + end + + tests("#create_subnet('#{@vpc_id}', '10.0.9.16/28')").raises(Fog::Compute::AWS::Error) do + Fog::Compute[:aws].create_subnet(@vpc_id, '10.0.9.16/28') + end + + # Attempt to create two subnets with conflicting CIDRs + tests("#create_subnet('#{@vpc_id}', '10.0.10.64/26')").raises(::Fog::Compute::AWS::Error) do + Fog::Compute[:aws].create_subnet(@vpc_id, '10.0.10.0/24') + Fog::Compute[:aws].create_subnet(@vpc_id, '10.0.10.64/26') + end + end + + @vpc.destroy end