diff --git a/lib/fog/aws/models/rds/server.rb b/lib/fog/aws/models/rds/server.rb index c6996ccb03..37aba2e4ce 100644 --- a/lib/fog/aws/models/rds/server.rb +++ b/lib/fog/aws/models/rds/server.rb @@ -38,7 +38,7 @@ class Server < Fog::Model attribute :tde_credential_arn, :aliases => 'TdeCredentialArn' attribute :vpc_security_groups, :aliases => 'VpcSecurityGroups', :type => :array - attr_accessor :password, :parameter_group_name, :security_group_names, :port + attr_accessor :password, :parameter_group_name, :security_group_names, :port, :source_snapshot_id def create_read_replica(replica_id, options={}) options[:security_group_names] ||= options['DBSecurityGroups'] @@ -104,16 +104,22 @@ def modify(immediately, options) end def save - requires :engine - requires :allocated_storage - requires :master_username - requires :password - - self.flavor_id ||= 'db.m1.small' - - data = service.create_db_instance(id, attributes_to_params) - merge_attributes(data.body['CreateDBInstanceResult']['DBInstance']) - true + if source_snapshot_id + requires :id + data = service.restore_db_instance_from_db_snapshot(source_snapshot_id, id, attributes_to_params) + merge_attributes(data.body['RestoreDBInstanceFromDBSnapshotResult']['DBInstance']) + else + requires :engine + requires :allocated_storage + requires :master_username + requires :password + + self.flavor_id ||= 'db.m1.small' + + data = service.create_db_instance(id, attributes_to_params) + merge_attributes(data.body['CreateDBInstanceResult']['DBInstance']) + true + end end # Converts attributes to a parameter hash suitable for requests diff --git a/lib/fog/aws/models/rds/servers.rb b/lib/fog/aws/models/rds/servers.rb index 0f6f62dba7..270ca12742 100644 --- a/lib/fog/aws/models/rds/servers.rb +++ b/lib/fog/aws/models/rds/servers.rb @@ -17,6 +17,10 @@ def get(identity) rescue Fog::AWS::RDS::NotFound nil end + + def restore(options) + create(options) + end end end end diff --git a/lib/fog/aws/models/rds/snapshot.rb b/lib/fog/aws/models/rds/snapshot.rb index b179a010e8..fdfa95c81b 100644 --- a/lib/fog/aws/models/rds/snapshot.rb +++ b/lib/fog/aws/models/rds/snapshot.rb @@ -12,6 +12,7 @@ class Snapshot < Fog::Model attribute :state, :aliases => 'Status' attribute :port, :aliases => 'Port', :type => :integer attribute :allocated_storage, :aliases => 'AllocatedStorage', :type => :integer + attribute :storage_type, :aliases => 'StorageType', :type => :string attribute :iops, :aliases => 'Iops', :type => :integer attribute :availability_zone, :aliases => 'AvailabilityZone' attribute :type, :aliases => 'SnapshotType' diff --git a/lib/fog/aws/parsers/rds/restore_db_instance_from_db_snapshot.rb b/lib/fog/aws/parsers/rds/restore_db_instance_from_db_snapshot.rb index db12ec8fe5..b50f8c5e65 100644 --- a/lib/fog/aws/parsers/rds/restore_db_instance_from_db_snapshot.rb +++ b/lib/fog/aws/parsers/rds/restore_db_instance_from_db_snapshot.rb @@ -6,7 +6,7 @@ module RDS class RestoreDBInstanceFromDBSnapshot < Fog::Parsers::AWS::RDS::DbParser def reset - @response = { 'RestoreDBInstanceFromDBSnapshot' => {}, 'ResponseMetadata' => {} } + @response = { 'RestoreDBInstanceFromDBSnapshotResult' => {}, 'ResponseMetadata' => {} } super end @@ -17,7 +17,7 @@ def start_element(name, attrs = []) def end_element(name) case name when 'DBInstance' - @response['RestoreDBInstanceFromDBSnapshot']['DBInstance'] = @db_instance + @response['RestoreDBInstanceFromDBSnapshotResult']['DBInstance'] = @db_instance @db_instance = fresh_instance when 'RequestId' @response['ResponseMetadata'][name] = value diff --git a/lib/fog/aws/requests/rds/create_db_instance.rb b/lib/fog/aws/requests/rds/create_db_instance.rb index b854d6a855..1bfccbc419 100644 --- a/lib/fog/aws/requests/rds/create_db_instance.rb +++ b/lib/fog/aws/requests/rds/create_db_instance.rb @@ -65,7 +65,7 @@ def create_db_instance(db_name, options={}) end # These are the required parameters according to the API - required_params = %w{AllocatedStorage DBInstanceClass Engine MasterUserPassword MasterUsername } + required_params = %w{AllocatedStorage DBInstanceClass Engine MasterUsername } required_params.each do |key| unless options.key?(key) and options[key] and !options[key].to_s.empty? #response.status = 400 diff --git a/lib/fog/aws/requests/rds/create_db_snapshot.rb b/lib/fog/aws/requests/rds/create_db_snapshot.rb index 57e87395af..732616b10f 100644 --- a/lib/fog/aws/requests/rds/create_db_snapshot.rb +++ b/lib/fog/aws/requests/rds/create_db_snapshot.rb @@ -45,7 +45,7 @@ def create_db_snapshot(identifier, name) 'InstanceCreateTime' => Time.now } # Copy attributes from server - %w(Engine EngineVersion AvailabilityZone AllocatedStorage Iops MasterUsername InstanceCreateTime).each do |key| + %w(Engine EngineVersion AvailabilityZone AllocatedStorage StorageType Iops MasterUsername InstanceCreateTime).each do |key| snapshot_data[key] = server_data[key] end snapshot_data['Port'] = server_data['Endpoint']['Port'] diff --git a/lib/fog/aws/requests/rds/modify_db_instance.rb b/lib/fog/aws/requests/rds/modify_db_instance.rb index 20d010767d..0338e49c6f 100644 --- a/lib/fog/aws/requests/rds/modify_db_instance.rb +++ b/lib/fog/aws/requests/rds/modify_db_instance.rb @@ -47,60 +47,62 @@ def modify_db_instance(db_name, apply_immediately, options={}) class Mock def modify_db_instance(db_name, apply_immediately, _options={}) - options = _options.dup + options = _options response = Excon::Response.new + if server = self.data[:servers][db_name] + if server["DBInstanceStatus"] != "available" + raise Fog::AWS::RDS::NotFound.new("DBInstance #{db_name} not available for modification") + else + self.data[:modify_time] = Time.now + # TODO verify the params options + # if apply_immediately is false, all the options go to pending_modified_values and then apply and clear after either + # a reboot or the maintainance window + #if apply_immediately + # modified_server = server.merge(options) + #else + # modified_server = server["PendingModifiedValues"].merge!(options) # it appends + #end + if options["NewDBInstanceIdentifier"] + options["DBInstanceIdentifier"] = options.delete("NewDBInstanceIdentifier") + end + + rds_security_groups = self.data[:security_groups].values + ec2_security_groups = Fog::Compute::AWS::Mock.data[@region][@aws_access_key_id][:security_groups].values + + db_security_group_names = Array(options.delete("DBSecurityGroups")) + db_security_groups = db_security_group_names.inject([]) do |r, group_name| + unless rds_security_groups.find { |sg| sg["DBSecurityGroupName"] == group_name } + raise Fog::AWS::RDS::Error.new("InvalidParameterValue => Invalid security group , groupId= , groupName=#{group_name}") + end + r << {"Status" => "active", "DBSecurityGroupName" => group_name } + end + + vpc_security_groups = Array(options.delete("VpcSecurityGroups")).inject([]) do |r, group_id| + unless ec2_security_groups.find { |sg| sg["groupId"] == group_id } + raise Fog::AWS::RDS::Error.new("InvalidParameterValue => Invalid security group , groupId=#{group_id} , groupName=") + end + + r << {"Status" => "active", "VpcSecurityGroupId" => group_id } + end + + options.merge!( + "DBSecurityGroups" => db_security_groups, + "VpcSecurityGroups" => vpc_security_groups + ) + + self.data[:servers][db_name]["PendingModifiedValues"].merge!(options) # it appends + self.data[:servers][db_name]["DBInstanceStatus"] = "modifying" + response.status = 200 + response.body = { + "ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id }, + "ModifyDBInstanceResult" => { "DBInstance" => self.data[:servers][db_name] } + } + response - server = self.data[:servers][db_name] - - if server.nil? - raise Fog::AWS::RDS::NotFound.new("DBInstance #{db_name} not found") - end - - if server["DBInstanceStatus"] != "available" - raise Fog::AWS::RDS::NotFound.new("DBInstance #{db_name} not available for modification") - end - - db_security_group_names = Array(options.delete("DBSecurityGroups")) - - rds_security_groups = self.data[:security_groups].values - - db_security_groups = db_security_group_names.map do |r, group_name| - unless rds_security_groups.find { |sg| sg["DBSecurityGroupName"] == group_name } - raise Fog::AWS::RDS::Error.new("InvalidParameterValue => Invalid security group , groupId= , groupName=#{group_name}") - end - r << {"Status" => "active", "DBSecurityGroupName" => group_name } - end - - ec2_security_groups = Fog::Compute::AWS::Mock.data[@region][@aws_access_key_id][:security_groups].values - - vpc_security_groups = Array(options.delete("VpcSecurityGroups")).map do |group_id| - unless ec2_security_groups.find { |sg| sg["groupId"] == group_id } - raise Fog::AWS::RDS::Error.new("InvalidParameterValue => Invalid security group , groupId=#{group_id} , groupName=") end - - {"Status" => "active", "VpcSecurityGroupId" => group_id } - end - - options.merge!( - "DBSecurityGroups" => db_security_groups, - "VpcSecurityGroups" => vpc_security_groups - ) - - if options["NewDBInstanceIdentifier"] - options["DBInstanceIdentifier"] = options.delete("NewDBInstanceIdentifier") + else + raise Fog::AWS::RDS::NotFound.new("DBInstance #{db_name} not found") end - - self.data[:modify_time] = Time.now - server["PendingModifiedValues"].merge!(options) - server["DBInstanceStatus"] = "modifying" - - response.status = 200 - response.body = { - "ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id }, - "ModifyDBInstanceResult" => { "DBInstance" => server } - } - - response end end end diff --git a/lib/fog/aws/requests/rds/restore_db_instance_from_db_snapshot.rb b/lib/fog/aws/requests/rds/restore_db_instance_from_db_snapshot.rb index e998d30659..48ff405922 100644 --- a/lib/fog/aws/requests/rds/restore_db_instance_from_db_snapshot.rb +++ b/lib/fog/aws/requests/rds/restore_db_instance_from_db_snapshot.rb @@ -20,8 +20,69 @@ def restore_db_instance_from_db_snapshot(snapshot_id, db_name, opts={}) end class Mock - def restore_db_instance_from_db_snapshot(snapshot_id, db_id, options={}) - Fog::Mock.not_implemented + def restore_db_instance_from_db_snapshot(snapshot_id, db_name, options={}) + + if self.data[:servers] and self.data[:servers][db_name] + raise Fog::AWS::RDS::IdentifierTaken.new("DBInstanceAlreadyExists #{response.body.to_s}") + end + + unless self.data[:snapshots] and snapshot = self.data[:snapshots][snapshot_id] + raise Fog::AWS::RDS::NotFound.new("DBSnapshotNotFound #{response.body.to_s}") + end + + if !!options["MultiAZ"] && !!options["AvailabilityZone"] + raise Fog::AWS::RDS::InvalidParameterCombination.new('Requesting a specific availability zone is not valid for Multi-AZ instances.') + end + + option_group_membership = + if option_group_name = options['OptionGroupName'] + [{ 'OptionGroupMembership' => + [{ 'OptionGroupName' => option_group_name, 'Status' => "pending-apply"}] }] + else + [{ 'OptionGroupMembership' => + [{ 'OptionGroupName' => 'default: mysql-5.6', 'Status' => "pending-apply"}] }] + end + + data = { + "AllocatedStorage" => snapshot['AllocatedStorage'], + "AutoMinorVersionUpgrade" => options['AutoMinorVersionUpgrade'].nil? ? true : options['AutoMinorVersionUpgrade'], + "AvailabilityZone" => options['AvailabilityZone'], + "BackupRetentionPeriod" => options['BackupRetentionPeriod'] || 1, + "CACertificateIdentifier" => 'rds-ca-2015', + "DBInstanceClass" => options['DBInstanceClass'] || 'db.m3.medium', + "DBInstanceIdentifier" => db_name, + "DBInstanceStatus" => 'creating', + "DBName" => options['DBName'], + "DBParameterGroups" => [{'DBParameterGroupName'=>'default.mysql5.5', 'ParameterApplyStatus'=>'in-sync'}], + "DBSecurityGroups" => [{'Status'=>'active', 'DBSecurityGroupName'=>'default'}], + "Endpoint" => {}, + "Engine" => options['Engine'] || snapshot['Engine'], + "EngineVersion" => options['EngineVersion'] || snapshot['EngineVersion'], + "InstanceCreateTime" => nil, + "Iops" => options['Iops'], + "LicenseModel" => options['LicenseModel'] || snapshot['LicenseModel'] || 'general-public-license', + "MasterUsername" => options['MasterUsername'] || snapshot['MasterUsername'], + "MultiAZ" => !!options['MultiAZ'], + "OptiongroupMemberships" => option_group_membership, + "PendingModifiedValues" => { 'MasterUserPassword' => '****' }, # This clears when is available + "PreferredBackupWindow" => '08:00-08:30', + "PreferredMaintenanceWindow" => 'mon:04:30-mon:05:00', + "PubliclyAccessible" => true, + "ReadReplicaDBInstanceIdentifiers" => [], + "StorageType" => options['StorageType'] || (options['Iops'] ? 'io1' : 'standard'), + "VpcSecurityGroups" => nil, + "StorageEncrypted" => false, + } + + self.data[:servers][db_name] = data + response = Excon::Response.new + response.body = + { "ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id }, + "RestoreDBInstanceFromDBSnapshotResult" => { "DBInstance" => data } + } + response.status = 200 + self.data[:servers][db_name]["InstanceCreateTime"] = Time.now + response end end end diff --git a/tests/models/rds/server_tests.rb b/tests/models/rds/server_tests.rb index 0da179cc60..34e19c5c53 100644 --- a/tests/models/rds/server_tests.rb +++ b/tests/models/rds/server_tests.rb @@ -6,6 +6,7 @@ @instance.wait_for(20*60) { ready? } @instance_with_encrypted_storage.wait_for(20*60) { ready? } + @final_snapshot_id = uniq_id('fog-test-snapshot') tests("#storage_encrypted") do returns(true) { @instance_with_encrypted_storage.storage_encrypted } @@ -114,12 +115,11 @@ replica && replica.destroy test("Destroying with a final snapshot") do - final_snapshot_id = uniq_id('fog-test-snapshot') @instance_with_final_snapshot.wait_for { ready? } - @instance_with_final_snapshot.destroy(final_snapshot_id) + @instance_with_final_snapshot.destroy(@final_snapshot_id) returns(true, "Final snapshot created") do - @final_snapshot = Fog::AWS[:rds].snapshots.get(final_snapshot_id) + @final_snapshot = Fog::AWS[:rds].snapshots.get(@final_snapshot_id) !@final_snapshot.nil? end diff --git a/tests/models/rds/servers_tests.rb b/tests/models/rds/servers_tests.rb index 454e305bf9..42e4321252 100644 --- a/tests/models/rds/servers_tests.rb +++ b/tests/models/rds/servers_tests.rb @@ -3,4 +3,22 @@ collection_tests(Fog::AWS[:rds].servers, rds_default_server_params) do @instance.wait_for { ready? } end + + tests("#restore").succeeds do + instance = Fog::AWS[:rds].servers.create(rds_default_server_params.merge(:id => uniq_id("fog-snapshot-test"))) + + snapshot_id = uniq_id('fog-snapshot-test') + @snapshot = instance.snapshots.create(:id => snapshot_id ) + instance.destroy + + db_name = uniq_id('fog-db-name') + @restore_instance = Fog::AWS[:rds].servers.restore('master_username' => instance.master_username, 'flavor_id' => 'db.m3.medium', 'source_snapshot_id' => snapshot_id, 'id' => uniq_id('restored-instance')) + end + + if Fog.mocking? && @restore_instance.respond_to?(:ready?) + @restore_instance.wait_for { ready? } + end + + @snapshot.destroy + @restore_instance.destroy end diff --git a/tests/requests/rds/helper.rb b/tests/requests/rds/helper.rb index 6c3cd4d4fd..2fb6a264a4 100644 --- a/tests/requests/rds/helper.rb +++ b/tests/requests/rds/helper.rb @@ -311,6 +311,12 @@ module Formats 'DBEngineVersions' => [DB_ENGINE_VERSION] } ) + + RESTORE_DB_INSTANCE_FROM_DB_SNAPSHOT = BASIC.merge({ + 'RestoreDBInstanceFromDBSnapshotResult' => { + 'DBInstance' => INSTANCE + } + }) end end end diff --git a/tests/requests/rds/instance_tests.rb b/tests/requests/rds/instance_tests.rb index b7769677e0..60ac62fd0a 100644 --- a/tests/requests/rds/instance_tests.rb +++ b/tests/requests/rds/instance_tests.rb @@ -5,10 +5,11 @@ # serveral minutes for deleted servers to disappear suffix = rand(65536).to_s(16) - @db_instance_id = "fog-test-#{suffix}" - @db_replica_id = "fog-replica-#{suffix}" - @db_snapshot_id = "fog-snapshot-#{suffix}" - @db_final_snapshot_id = "fog-final-snapshot-#{suffix}" + @db_instance_id = "fog-test-#{suffix}" + @db_replica_id = "fog-replica-#{suffix}" + @db_snapshot_id = "fog-snapshot-#{suffix}" + @db_final_snapshot_id = "fog-final-snapshot-#{suffix}" + @db_instance_restore_id = "fog-test-#{suffix}" tests('success') do @@ -109,6 +110,17 @@ body end + tests("#restore_db_instance_from_db_snapshot").formats(AWS::RDS::Formats::RESTORE_DB_INSTANCE_FROM_DB_SNAPSHOT) do + snapshot = Fog::AWS[:rds].snapshots.get(@db_final_snapshot_id) + snapshot.wait_for { state == 'available' } + result = Fog::AWS[:rds].restore_db_instance_from_db_snapshot(@db_final_snapshot_id, @db_instance_restore_id).body + instance = result['RestoreDBInstanceFromDBSnapshotResult']['DBInstance'] + returns('creating') { instance['DBInstanceStatus'] } + result + end + restore_server = Fog::AWS[:rds].servers.get(@db_instance_restore_id) + restore_server.wait_for { state == 'available' } + tests("#delete_db_snapshot").formats(AWS::RDS::Formats::DELETE_DB_SNAPSHOT) do Fog::AWS[:rds].snapshots.get(@db_snapshot_id).wait_for { ready? } Fog::AWS[:rds].delete_db_snapshot(@db_snapshot_id).body