1
0
Fork 0
mirror of https://github.com/fog/fog-aws.git synced 2022-11-09 13:50:52 -05:00

support for rds clusters and aurora

* add db cluster identifier to db instance parser
* dont allow setting security groups on aurora instances
* aurora instances do not support the multi az parameter due to the cluster
* aurora has its own storage type that cannot be set
This commit is contained in:
Eugene Howe 2015-12-14 08:45:31 -05:00
parent 03b1c0f90f
commit 2980ba7a52
34 changed files with 1114 additions and 42 deletions

View file

@ -0,0 +1,93 @@
module Fog
module AWS
class RDS
class Cluster < Fog::Model
identity :id, :aliases => 'DBClusterIdentifier'
attribute :allocated_storage, :aliases => 'AllocatedStorage', :type => :integer
attribute :backup_retention_period, :aliases => 'BackupRetentionPeriod', :type => :integer
attribute :db_cluster_members, :aliases => 'DBClusterMembers', :type => :array
attribute :db_cluster_parameter_group, :aliases => 'DBClusterParameterGroup'
attribute :db_subnet_group, :aliases => 'DBSubnetGroup'
attribute :endpoint, :aliases => 'Endpoint'
attribute :engine, :aliases => 'Engine'
attribute :engine_version, :aliases => 'EngineVersion'
attribute :password, :aliases => 'MasterUserPassword'
attribute :master_username, :aliases => 'MasterUsername'
attribute :port, :aliases => 'Port', :type => :integer
attribute :preferred_backup_window, :aliases => 'PreferredBackupWindow'
attribute :preferred_maintenance_window, :aliases => 'PreferredMaintenanceWindow'
attribute :state, :aliases => 'Status'
attribute :vpc_security_groups, :aliases => 'VpcSecurityGroups'
attr_accessor :storage_encrypted #not in the response
def ready?
state == "available"
end
def snapshots
requires :id
service.cluster_snapshots(:cluster => self)
end
def servers(set=db_cluster_members)
set.map do |member|
service.servers.get(member['DBInstanceIdentifier'])
end
end
def master
db_cluster_members.detect { |member| member["master"] }
end
def replicas
servers(db_cluster_members.select { |member| !member["master"] })
end
def has_replica?(replica_name)
replicas.detect { |replica| replica.id == replica_name }
end
def destroy(snapshot_identifier=nil)
requires :id
service.delete_db_cluster(id, snapshot_identifier, snapshot_identifier.nil?)
true
end
def save
requires :id
requires :engine
requires :master_username
requires :password
data = service.create_db_cluster(id, attributes_to_params)
merge_attributes(data.body['CreateDBClusterResult']['DBCluster'])
true
end
def attributes_to_params
options = {
'AllocatedStorage' => allocated_storage,
'BackupRetentionPeriod' => backup_retention_period,
'DBClusterIdentifier' => identity,
'DBClusterParameterGroup' => db_cluster_parameter_group,
'DBSubnetGroup' => db_subnet_group,
'Endpoint' => endpoint,
'Engine' => engine,
'EngineVersion' => engine_version,
'MasterUserPassword' => password,
'MasterUsername' => master_username,
'PreferredBackupWindow' => preferred_backup_window,
'PreferredMaintenanceWindow' => preferred_maintenance_window,
'Status' => state,
'StorageEncrypted' => storage_encrypted,
'VpcSecurityGroups' => vpc_security_groups,
}
options.delete_if { |key,value| value.nil? }
end
end
end
end
end

View file

@ -0,0 +1,48 @@
require 'fog/aws/models/rds/snapshot'
module Fog
module AWS
class RDS
class ClusterSnapshots < Fog::Collection
attribute :cluster
attribute :filters
model Fog::AWS::RDS::Snapshot
def initialize(attributes)
self.filters ||= {}
if attributes[:cluster]
filters[:identifier] = attributes[:cluster].id
end
if attributes[:type]
filters[:type] = attributes[:type]
end
super
end
def all(filters_arg = filters)
filters.merge!(filters_arg)
page = service.describe_db_cluster_snapshots(filters).body['DescribeDBClusterSnapshotsResult']
filters[:marker] = page['Marker']
load(page['DBClusterSnapshots'])
end
def get(identity)
data = service.describe_db_cluster_snapshots(:snapshot_id => identity).body['DescribeDBClusterSnapshotsResult']['DBClusterSnapshots'].first
new(data) # data is an attribute hash
rescue Fog::AWS::RDS::NotFound
nil
end
def create(params={})
if cluster
super(params.merge(cluster_id: cluster.id))
else
super(params)
end
end
end
end
end
end

View file

@ -0,0 +1,23 @@
require 'fog/aws/models/rds/cluster'
module Fog
module AWS
class RDS
class Clusters < Fog::Collection
model Fog::AWS::RDS::Cluster
def all
data = service.describe_db_clusters.body['DescribeDBClustersResult']['DBClusters']
load(data)
end
def get(identity)
data = service.describe_db_clusters(identity).body['DescribeDBClustersResult']['DBClusters'].first
new(data)
rescue Fog::AWS::RDS::NotFound
nil
end
end
end
end
end

View file

@ -10,6 +10,7 @@ module Fog
attribute :backup_retention_period, :aliases => 'BackupRetentionPeriod', :type => :integer
attribute :ca_certificate_id, :aliases => 'CACertificateIdentifier'
attribute :character_set_name, :aliases => 'CharacterSetName'
attribute :cluster_id, :aliases => 'DBClusterIdentifier'
attribute :created_at, :aliases => 'InstanceCreateTime', :type => :time
attribute :db_name, :aliases => 'DBName'
attribute :db_parameter_groups, :aliases => 'DBParameterGroups'
@ -40,6 +41,11 @@ module Fog
attr_accessor :password, :parameter_group_name, :security_group_names, :port, :source_snapshot_id
def cluster
return nil unless cluster_id
service.clusters.get(cluster_id)
end
def create_read_replica(replica_id, options={})
options[:security_group_names] ||= options['DBSecurityGroups']
params = self.class.new(options).attributes_to_params
@ -110,11 +116,16 @@ module Fog
merge_attributes(data.body['RestoreDBInstanceFromDBSnapshotResult']['DBInstance'])
else
requires :engine
requires :allocated_storage
requires :master_username
requires :password
self.flavor_id ||= 'db.m1.small'
if engine == 'aurora'
requires :cluster_id
self.flavor_id ||= 'db.r3.large'
else
requires :master_username
requires :password
requires :allocated_storage
self.flavor_id ||= 'db.m1.small'
end
data = service.create_db_instance(id, attributes_to_params)
merge_attributes(data.body['CreateDBInstanceResult']['DBInstance'])
@ -129,6 +140,7 @@ module Fog
'AutoMinorVersionUpgrade' => auto_minor_version_upgrade,
'AvailabilityZone' => availability_zone,
'BackupRetentionPeriod' => backup_retention_period,
'DBClusterIdentifier' => cluster_id,
'DBInstanceClass' => flavor_id,
'DBInstanceIdentifier' => id,
'DBName' => db_name,

View file

@ -2,21 +2,25 @@ module Fog
module AWS
class RDS
class Snapshot < Fog::Model
identity :id, :aliases => ['DBSnapshotIdentifier', :name]
attribute :instance_id, :aliases => 'DBInstanceIdentifier'
attribute :created_at, :aliases => 'SnapshotCreateTime', :type => :time
attribute :instance_created_at, :aliases => 'InstanceCreateTime', :type => :time
attribute :engine, :aliases => 'Engine'
attribute :engine_version, :aliases => 'EngineVersion'
attribute :master_username, :aliases => 'MasterUsername'
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'
attribute :publicly_accessible, :aliases => 'PubliclyAccessible'
identity :id, :aliases => ['DBSnapshotIdentifier', 'DBClusterSnapshotIdentifier', :name]
attribute :allocated_storage, :aliases => 'AllocatedStorage', :type => :integer
attribute :availability_zone, :aliases => 'AvailabilityZone'
attribute :cluster_created_at, :aliases => 'ClusterCreateTime', :type => :time
attribute :cluster_id, :aliases => 'DBClusterIdentifier'
attribute :created_at, :aliases => 'SnapshotCreateTime', :type => :time
attribute :engine, :aliases => 'Engine'
attribute :engine_version, :aliases => 'EngineVersion'
attribute :instance_created_at, :aliases => 'InstanceCreateTime', :type => :time
attribute :instance_id, :aliases => 'DBInstanceIdentifier'
attribute :iops, :aliases => 'Iops', :type => :integer
attribute :license_model, :aliases => 'LicenseModel'
attribute :master_username, :aliases => 'MasterUsername'
attribute :port, :aliases => 'Port', :type => :integer
attribute :publicly_accessible, :aliases => 'PubliclyAccessible'
attribute :state, :aliases => 'Status'
attribute :storage_type, :aliases => 'StorageType'
attribute :type, :aliases => 'SnapshotType'
def ready?
state == 'available'
@ -24,16 +28,25 @@ module Fog
def destroy
requires :id
requires_one :instance_id, :cluster_id
service.delete_db_snapshot(id)
if instance_id
service.delete_db_snapshot(id)
else
service.delete_db_cluster_snapshot(id)
end
true
end
def save
requires :instance_id
requires_one :instance_id, :cluster_id
requires :id
data = service.create_db_snapshot(instance_id, id).body['CreateDBSnapshotResult']['DBSnapshot']
data = if instance_id
service.create_db_snapshot(instance_id, id).body['CreateDBSnapshotResult']['DBSnapshot']
elsif cluster_id
service.create_db_cluster_snapshot(cluster_id, id).body['CreateDBClusterSnapshotResult']['DBClusterSnapshot']
end
merge_attributes(data)
true
end
@ -42,6 +55,11 @@ module Fog
requires :instance_id
service.servers.get(instance_id)
end
def cluster
requires :cluster_id
service.clusters.get(cluster_id)
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Parsers
module AWS
module RDS
require 'fog/aws/parsers/rds/db_cluster_parser'
class CreateDBCluster < Fog::Parsers::AWS::RDS::DbClusterParser
def reset
@response = { 'CreateDBClusterResult' => {}, 'ResponseMetadata' => {} }
super
end
def start_element(name, attrs = [])
super
end
def end_element(name)
case name
when 'DBCluster'
@response['CreateDBClusterResult']['DBCluster'] = @db_cluster
@db_cluster = fresh_cluster
when 'RequestId'
@response['ResponseMetadata'][name] = value
else
super
end
end
end
end
end
end
end

View file

@ -0,0 +1,33 @@
module Fog
module Parsers
module AWS
module RDS
require 'fog/aws/parsers/rds/db_cluster_snapshot_parser'
class CreateDBClusterSnapshot < Fog::Parsers::AWS::RDS::DBClusterSnapshotParser
def reset
@response = { 'CreateDBClusterSnapshotResult' => {}, 'ResponseMetadata' => {} }
super
end
def start_element(name, attrs = [])
super
end
def end_element(name)
case name
when 'DBClusterSnapshot'
@response['CreateDBClusterSnapshotResult']['DBClusterSnapshot'] = @db_cluster_snapshot
@db_cluster_snapshot = fresh_snapshot
when 'RequestId'
@response['ResponseMetadata'][name] = value
else
super
end
end
end
end
end
end
end

View file

@ -0,0 +1,71 @@
module Fog
module Parsers
module AWS
module RDS
class DbClusterParser < Fog::Parsers::Base
def reset
@db_cluster = fresh_cluster
end
def fresh_cluster
{'AvailabilityZones' => [], 'VpcSecurityGroups' => []}
end
def start_element(name, attrs=[])
super
case name
when 'AvailabilityZones'
@in_availability_zones = true
when 'DBClusterMembers'
@in_db_cluster_members = true
@db_cluster_members = []
when 'DBClusterMember'
@db_cluster_member = {}
when 'VpcSecurityGroupMembership'
@vpc_security_group = {}
when 'VpcSecurityGroups'
@in_vpc_security_groups = true
@vpc_security_groups = []
end
end
def end_element(name)
case name
when 'Port', 'Engine', 'Status', 'BackupRetentionPeriod', 'DBSubnetGroup', 'EngineVersion', 'Endpoint', 'DBClusterParameterGroup', 'DBClusterIdentifier', 'PreferredBackupWindow', 'PreferredMaintenanceWindow', 'AllocatedStorage', 'MasterUsername'
@db_cluster[name] = value
when 'VpcSecurityGroups'
@in_vpc_security_groups = false
@db_cluster['VpcSecurityGroups'] = @vpc_security_groups
when 'VpcSecurityGroupMembership'
@vpc_security_groups << @vpc_security_group
@vpc_security_group = {}
when 'VpcSecurityGroupId'
@vpc_security_group[name] = value
when 'Status'
# Unfortunately, status is used in VpcSecurityGroupMemebership and
# DBSecurityGroups
if @in_db_security_groups
@db_security_group[name]=value
end
if @in_vpc_security_groups
@vpc_security_group[name] = value
end
when 'DBClusterMembers'
@in_db_cluster_members = false
@db_cluster['DBClusterMembers'] = @db_cluster_members
when 'DBClusterMember'
@db_cluster_members << @db_cluster_member
@db_cluster_member = {}
when 'IsClusterWriter'
@db_cluster_member['master'] = value == "true"
when 'DBInstanceIdentifier'
@db_cluster_member[name] = value
when 'DBCluster'
@db_cluster = fresh_cluster
end
end
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Parsers
module AWS
module RDS
class DBClusterSnapshotParser < Fog::Parsers::Base
def reset
@db_cluster_snapshot = fresh_snapshot
end
def fresh_snapshot
{}
end
def start_element(name, attrs=[])
super
end
def end_element(name)
case name
when 'Port', 'PercentProgress', 'AllocatedStorage'
@db_cluster_snapshot[name] = value.to_i
when 'SnapshotCreateTime', 'ClusterCreateTime'
@db_cluster_snapshot[name] = Time.parse(value)
else
@db_cluster_snapshot[name] = value
end
end
end
end
end
end
end

View file

@ -48,7 +48,8 @@ module Fog
'AvailabilityZone', 'MasterUsername', 'DBName', 'LicenseModel',
'DBSubnetGroupName', 'StorageType', 'KmsKeyId', 'TdeCredentialArn',
'SecondaryAvailabilityZone', 'DbiResourceId', 'CACertificateIdentifier',
'CharacterSetName', 'DbiResourceId', 'LicenseModel', 'KmsKeyId'
'CharacterSetName', 'DbiResourceId', 'LicenseModel', 'KmsKeyId',
'DBClusterIdentifier'
@db_instance[name] = value
when 'MultiAZ', 'AutoMinorVersionUpgrade', 'PubliclyAccessible', 'StorageEncrypted'
@db_instance[name] = (value == 'true')

View file

@ -0,0 +1,32 @@
module Fog
module Parsers
module AWS
module RDS
require 'fog/aws/parsers/rds/db_cluster_parser'
class DeleteDBCluster < Fog::Parsers::AWS::RDS::DbClusterParser
def reset
@response = { 'DeleteDBClusterResult' => {}, 'ResponseMetadata' => {} }
super
end
def start_element(name, attrs = [])
super
end
def end_element(name)
case name
when 'DBCluster'
@response['DeleteDBClusterResult']['DBCluster'] = @db_cluster
@db_cluster = fresh_cluster
when 'RequestId'
@response['ResponseMetadata'][name] = value
else
super
end
end
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Parsers
module AWS
module RDS
require 'fog/aws/parsers/rds/db_cluster_snapshot_parser'
class DeleteDBClusterSnapshot < Fog::Parsers::AWS::RDS::DBClusterSnapshotParser
def reset
@response = {'DeleteDBClusterSnapshotResult' => {}, 'ResponseMetadata' => {} }
super
end
def start_element(name, attrs = [])
super
end
def end_element(name)
case name
when 'RequestId'
@response['ResponseMetadata'][name] = value
when 'DBClusterSnapshot'
@response['DeleteDBClusterSnapshotResult']['DBClusterSnapshot'] = @db_cluster_snapshot
@db_cluster_snapshot = fresh_snapshot
else
super
end
end
end
end
end
end
end

View file

@ -0,0 +1,34 @@
module Fog
module Parsers
module AWS
module RDS
require 'fog/aws/parsers/rds/db_cluster_snapshot_parser'
class DescribeDBClusterSnapshots < Fog::Parsers::AWS::RDS::DBClusterSnapshotParser
def reset
@response = {'DescribeDBClusterSnapshotsResult' => {'DBClusterSnapshots' => []}, 'ResponseMetadata' => {}}
super
end
def start_element(name, attrs = [])
super
end
def end_element(name)
case name
when 'DBClusterSnapshot'
@response['DescribeDBClusterSnapshotsResult']['DBClusterSnapshots'] << @db_cluster_snapshot
@db_cluster_snapshot = fresh_snapshot
when 'Marker'
@response['DescribeDBClusterSnapshotsResult']['Marker'] = value
when 'RequestId'
@response['ResponseMetadata'][name] = value
else
super
end
end
end
end
end
end
end

View file

@ -0,0 +1,34 @@
module Fog
module Parsers
module AWS
module RDS
require 'fog/aws/parsers/rds/db_cluster_parser'
class DescribeDBClusters < Fog::Parsers::AWS::RDS::DbClusterParser
def reset
@response = { 'DescribeDBClustersResult' => { 'DBClusters' => []}, 'ResponseMetadata' => {} }
super
end
def start_element(name, attrs=[])
super
end
def end_element(name)
case name
when 'DBCluster'
@response['DescribeDBClustersResult']['DBClusters'] << @db_cluster
@db_cluster = fresh_cluster
when 'Marker'
@response['DescribeDBClustersResult']['Marker'] = value
when 'RequestId'
@response['ResponseMetadata'][name] = value
else
super
end
end
end
end
end
end
end

View file

@ -17,19 +17,12 @@ module Fog
def end_element(name)
case name
when 'AllocatedStorage' then @db_snapshot['AllocatedStorage'] = value.to_i
when 'AvailabilityZone' then @db_snapshot['AvailabilityZone'] = value
when 'DBInstanceIdentifier' then @db_snapshot['DBInstanceIdentifier'] = value
when 'DBSnapshotIdentifier' then @db_snapshot['DBSnapshotIdentifier'] = value
when 'Engine' then @db_snapshot['Engine'] = value
when 'EngineVersion' then @db_snapshot['EngineVersion'] = value
when 'InstanceCreateTime' then @db_snapshot['InstanceCreateTime'] = Time.parse value
when 'Iops' then @db_snapshot['Iops'] = value
when 'MasterUsername' then @db_snapshot['MasterUsername'] = value
when 'Port' then @db_snapshot['Port'] = value.to_i
when 'SnapshotCreateTime' then @db_snapshot['SnapshotCreateTime'] = Time.parse value
when 'SnapshotType' then @db_snapshot['SnapshotType'] = value
when 'Status' then @db_snapshot['Status'] = value
when 'AllocatedStorage', 'Port'
@db_snapshot[name] = value.to_i
when 'InstanceCreateTime', 'SnapshotCreateTime'
@db_snapshot[name] = Time.parse(value)
else
@db_snapshot[name] = value
end
end
end

View file

@ -64,10 +64,21 @@ module Fog
request :describe_engine_default_parameters
request :describe_db_clusters
request :describe_db_cluster_snapshots
request :create_db_cluster
request :create_db_cluster_snapshot
request :delete_db_cluster
request :delete_db_cluster_snapshot
model_path 'fog/aws/models/rds'
model :server
collection :servers
model :cluster
collection :clusters
collection :cluster_snapshots
model :snapshot
collection :snapshots
@ -97,6 +108,8 @@ module Fog
@data ||= Hash.new do |hash, region|
hash[region] = Hash.new do |region_hash, key|
region_hash[key] = {
:clusters => {},
:cluster_snapshots => {},
:servers => {},
:security_groups => {},
:subnet_groups => {},

View file

@ -0,0 +1,69 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/create_db_cluster'
def create_db_cluster(cluster_name, options={})
if security_groups = options.delete('VpcSecurityGroups')
options.merge!(Fog::AWS.indexed_param('VpcSecurityGroupIds.member.%d', [*security_groups]))
end
request({
'Action' => 'CreateDBCluster',
'DBClusterIdentifier' => cluster_name,
:parser => Fog::Parsers::AWS::RDS::CreateDBCluster.new,
}.merge(options))
end
end
class Mock
def create_db_cluster(cluster_name, options={})
response = Excon::Response.new
if self.data[:clusters][cluster_name]
raise Fog::AWS::RDS::IdentifierTaken.new("DBClusterAlreadyExists")
end
required_params = %w(Engine MasterUsername MasterUserPassword)
required_params.each do |key|
unless options.key?(key) && options[key] && !options[key].to_s.empty?
raise Fog::AWS::RDS::NotFound.new("The request must contain the parameter #{key}")
end
end
vpc_security_groups = Array(options.delete("VpcSecurityGroups")).map do |group_id|
{"VpcSecurityGroupId" => group_id }
end
data = {
'AllocatedStorage' => "1",
'BackupRetentionPeriod' => (options["BackupRetentionPeriod"] || 35).to_s,
'ClusterCreateTime' => Time.now,
'DBClusterIdentifier' => cluster_name,
'DBClusterMembers' => [],
'DBClusterParameterGroup' => options['DBClusterParameterGroup'] || "default.aurora5.6",
'DBSubnetGroup' => options["DBSubnetGroup"] || "default",
'Endpoint' => "#{cluster_name}.cluster-#{Fog::Mock.random_hex(8)}.#{@region}.rds.amazonaws.com",
'Engine' => options["Engine"] || "aurora5.6",
'EngineVersion' => options["EngineVersion"] || "5.6.10a",
'MasterUsername' => options["MasterUsername"],
'Port' => options["Port"] || "3306",
'PreferredBackupWindow' => options["PreferredBackupWindow"] || "04:45-05:15",
'PreferredMaintenanceWindow' => options["PreferredMaintenanceWindow"] || "sat:05:56-sat:06:26",
'Status' => "available",
'StorageEncrypted' => options["StorageEncrypted"] || false,
'VpcSecurityGroups' => vpc_security_groups,
}
self.data[:clusters][cluster_name] = data
response.body = {
"ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id },
"CreateDBClusterResult" => { "DBCluster" => data.dup.reject { |k,v| k == 'ClusterCreateTime' } }
}
response.status = 200
response
end
end
end
end
end

View file

@ -0,0 +1,56 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/create_db_cluster_snapshot'
def create_db_cluster_snapshot(identifier, name)
request(
'Action' => 'CreateDBClusterSnapshot',
'DBClusterIdentifier' => identifier,
'DBClusterSnapshotIdentifier' => name,
:parser => Fog::Parsers::AWS::RDS::CreateDBClusterSnapshot.new
)
end
end
class Mock
def create_db_cluster_snapshot(identifier, name)
response = Excon::Response.new
if data[:cluster_snapshots][name]
raise Fog::AWS::RDS::IdentifierTaken.new
end
cluster = self.data[:clusters][identifier]
raise Fog::AWS::RDS::NotFound.new("DBCluster #{identifier} not found") unless cluster
data = {
'AllocatedStorage' => cluster['AllocatedStorage'].to_i,
'ClusterCreateTime' => cluster['ClusterCreateTime'],
'DBClusterIdentifier' => identifier,
'DBClusterSnapshotIdentifier' => name,
'Engine' => cluster['Engine'],
'EngineVersion' => cluster['EngineVersion'],
'LicenseModel' => cluster['Engine'],
'MasterUsername' => cluster['MasterUsername'],
'SnapshotCreateTime' => Time.now,
'SnapshotType' => 'manual',
'StorageEncrypted' => cluster["StorageEncrypted"],
'Status' => 'creating',
}
self.data[:cluster_snapshots][name] = data
response.body = {
"ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id },
"CreateDBClusterSnapshotResult" => { "DBClusterSnapshot" => data.dup },
}
self.data[:cluster_snapshots][name]['SnapshotCreateTime'] = Time.now
response
end
end
end
end
end

View file

@ -65,7 +65,8 @@ module Fog
end
# These are the required parameters according to the API
required_params = %w{AllocatedStorage DBInstanceClass Engine MasterUsername }
required_params = %w(DBInstanceClass Engine)
required_params += %w{AllocatedStorage DBInstanceClass Engine MasterUserPassword MasterUsername } unless options["DBClusterIdentifier"]
required_params.each do |key|
unless options.key?(key) and options[key] and !options[key].to_s.empty?
#response.status = 400
@ -119,12 +120,39 @@ module Fog
{"Status" => "active", "VpcSecurityGroupId" => group_id }
end
if options["Engine"] == "aurora" && ! options["DBClusterIdentifier"]
raise Fog::AWS::RDS::Error.new("InvalidParameterStateValue => Standalone instances for this engine are not supported")
end
if cluster_id = options["DBClusterIdentifier"]
if vpc_security_groups.any?
raise Fog::AWS::RDS::Error.new("InvalidParameterCombination => The requested DB Instance will be a member of a DB Cluster and its vpc security group should not be set directly.")
end
if options["MultiAZ"]
raise Fog::AWS::RDS::Error.new("InvalidParameterCombination => VPC Multi-AZ DB Instances are not available for engine: aurora")
end
%w(AllocatedStorage BackupRetentionPeriod MasterUsername MasterUserPassword).each do |forbidden|
raise Fog::AWS::RDS::Error.new("InvalidParameterCombination => The requested DB Instance will be a member of a DB Cluster and its #{forbidden} should not be set directly.") if options[forbidden]
end
options["StorageType"] = "aurora"
cluster = self.data[:clusters][cluster_id]
member = {"DBInstanceIdentifier" => db_name, "master" => cluster['DBClusterMembers'].empty?}
cluster['DBClusterMembers'] << member
self.data[:clusters][cluster_id] = cluster
end
data = {
"AllocatedStorage" => options["AllocatedStorage"],
"AutoMinorVersionUpgrade" => options["AutoMinorVersionUpgrade"].nil? ? true : options["AutoMinorVersionUpgrade"],
"AvailabilityZone" => options["AvailabilityZone"],
"BackupRetentionPeriod" => options["BackupRetentionPeriod"] || 1,
"CACertificateIdentifier" => "rds-ca-2015",
"DBClusterIdentifier" => options["DBClusterIdentifier"],
"DBInstanceClass" => options["DBInstanceClass"],
"DBInstanceIdentifier" => db_name,
"DBInstanceStatus" =>"creating",
@ -138,14 +166,14 @@ module Fog
"InstanceCreateTime" => nil,
"Iops" => options["Iops"],
"LicenseModel" => "general-public-license",
"MasterUsername" => options["MasterUsername"],
"MasterUsername" => cluster_id ? cluster["MasterUsername"] : options["MasterUsername"],
"MultiAZ" => !!options["MultiAZ"],
"PendingModifiedValues" => { "MasterUserPassword" => "****" }, # This clears when is available
"PreferredBackupWindow" => options["PreferredBackupWindow"] || "08:00-08:30",
"PreferredMaintenanceWindow" => options["PreferredMaintenanceWindow"] || "mon:04:30-mon:05:00",
"PubliclyAccessible" => !!options["PubliclyAccessible"],
"ReadReplicaDBInstanceIdentifiers" => [],
"StorageEncrypted" => options["StorageEncrypted"] || false,
"StorageEncrypted" => cluster_id ? cluster["StorageEncrypted"] : (options["StorageEncrypted"] || false),
"StorageType" => options["StorageType"] || "standard",
"VpcSecurityGroups" => vpc_security_groups,
}

View file

@ -45,7 +45,7 @@ module Fog
'InstanceCreateTime' => Time.now
}
# Copy attributes from server
%w(Engine EngineVersion AvailabilityZone AllocatedStorage StorageType Iops MasterUsername InstanceCreateTime).each do |key|
%w(Engine EngineVersion AvailabilityZone AllocatedStorage Iops MasterUsername InstanceCreateTime StorageType).each do |key|
snapshot_data[key] = server_data[key]
end
snapshot_data['Port'] = server_data['Endpoint']['Port']

View file

@ -0,0 +1,42 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/delete_db_cluster'
def delete_db_cluster(identifier, snapshot_identifier, skip_snapshot = false)
params = {}
params["FinalDBSnapshotIdentifier"] = snapshot_identifier if snapshot_identifier
request({
'Action' => 'DeleteDBCluster',
'DBClusterIdentifier' => identifier,
'SkipFinalSnapshot' => skip_snapshot,
}.merge(params))
end
end
class Mock
def delete_db_cluster(identifier, snapshot_identifier, skip_snapshot = false)
response = Excon::Response.new
cluster = self.data[:clusters][identifier] || raise(Fog::AWS::RDS::NotFound.new("DBCluster #{identifier} not found"))
raise Fog::AWS::RDS::Error.new("InvalidDBClusterStateFault => Cluster cannot be deleted, it still contains DB instances in non-deleting state.") if cluster["DBClusterMembers"].any?
unless skip_snapshot
create_db_cluster_snapshot(identifier, snapshot_identifier)
end
self.data[:clusters].delete(identifier)
response.status = 200
response.body = {
"ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id },
"DeleteDBClusterResult" => { "DBCluster" => cluster}
}
response
end
end
end
end
end

View file

@ -0,0 +1,33 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/delete_db_cluster_snapshot'
def delete_db_cluster_snapshot(name)
request(
'Action' => 'DeleteDBClusterSnapshot',
'DBClusterSnapshotIdentifier' => name,
:parser => Fog::Parsers::AWS::RDS::DeleteDBClusterSnapshot.new
)
end
end
class Mock
def delete_db_cluster_snapshot(name)
response = Excon::Response.new
snapshot = self.data[:cluster_snapshots].delete(name)
raise Fog::AWS::RDS::NotFound.new("DBClusterSnapshotNotFound => #{name} not found") unless snapshot
response.status = 200
response.body = {
"ResponseMetadata" => {"RequestId" => Fog::AWS::Mock.request_id},
"DeleteDBClusterSnapshotResult" => {"DBClusterSnapshot" => snapshot}
}
response
end
end
end
end
end

View file

@ -37,11 +37,20 @@ module Fog
unless skip_snapshot
if server_set["ReadReplicaSourceDBInstanceIdentifier"]
raise Fog::AWS::RDS::Error.new("InvalidParameterCombination => FinalDBSnapshotIdentifier can not be specified when deleting a replica instance")
elsif server_set["DBClusterIdentifier"]
raise Fog::AWS::RDS::Error.new("InvalidParameterCombination => FinalDBSnapshotIdentifier can not be specified when deleting a cluster instance")
else
create_db_snapshot(identifier, snapshot_identifier)
end
end
cluster = self.data[:clusters].values.detect { |c| c["DBClusterMembers"].any? { |m| m["DBInstanceIdentifier"] == identifier } }
if cluster
cluster["DBClusterMembers"].delete_if { |v| v["DBInstanceIdentifier"] == identifier }
self.data[:clusters][cluster["DBClusterIdentifier"]] = cluster
end
self.data[:servers].delete(identifier)
response.status = 200

View file

@ -26,8 +26,9 @@ module Fog
# TODO: raise error if snapshot isn't 'available'
response = Excon::Response.new
snapshot_data = self.data[:snapshots].delete(name)
snapshot_data = self.data[:cluster_snapshots].delete(name) unless snapshot_data
raise Fog::AWS::RDS::NotFound.new("DBSnapshtoNotFound => #{name} not found") unless snapshot_data
raise Fog::AWS::RDS::NotFound.new("DBSnapshotNotFound => #{name} not found") unless snapshot_data
response.status = 200
response.body = {

View file

@ -0,0 +1,54 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/describe_db_cluster_snapshots'
def describe_db_cluster_snapshots(opts={})
params = {}
params['SnapshotType'] = opts[:type] if opts[:type]
params['DBClusterIdentifier'] = opts[:identifier] if opts[:identifier]
params['DBClusterSnapshotIdentifier'] = opts[:snapshot_id] if opts[:snapshot_id]
params['Marker'] = opts[:marker] if opts[:marker]
params['MaxRecords'] = opts[:max_records] if opts[:max_records]
request({
'Action' => 'DescribeDBClusterSnapshots',
:parser => Fog::Parsers::AWS::RDS::DescribeDBClusterSnapshots.new
}.merge(params))
end
end
class Mock
def describe_db_cluster_snapshots(opts={})
response = Excon::Response.new
snapshots = self.data[:cluster_snapshots].values
if opts[:identifier]
snapshots = snapshots.select { |snapshot| snapshot['DBClusterIdentifier'] == opts[:identifier] }
end
if opts[:snapshot_id]
snapshots = snapshots.select { |snapshot| snapshot['DBClusterSnapshotIdentifier'] == opts[:snapshot_id] }
raise Fog::AWS::RDS::NotFound.new("DBClusterSnapshot #{opts[:snapshot_id]} not found") if snapshots.empty?
end
snapshots.each do |snapshot|
case snapshot['Status']
when 'creating'
if Time.now - snapshot['SnapshotCreateTime'] > Fog::Mock.delay
snapshot['Status'] = 'available'
end
end
end
response.status = 200
response.body = {
'ResponseMetadata' => { "RequestId" => Fog::AWS::Mock.request_id },
'DescribeDBClusterSnapshotsResult' => { 'DBClusterSnapshots' => snapshots }
}
response
end
end
end
end
end

View file

@ -0,0 +1,45 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/describe_db_clusters'
def describe_db_clusters(identifier=nil, opts={})
params = {}
params['DBClusterIdentifier'] = identifier if identifier
params['Marker'] = opts[:marker] if opts[:marker]
params['MaxRecords'] = opts[:max_records] if opts[:max_records]
request({
'Action' => 'DescribeDBClusters',
:parser => Fog::Parsers::AWS::RDS::DescribeDBClusters.new
}.merge(params))
end
end
class Mock
def describe_db_clusters(identifier=nil, opts={})
response = Excon::Response.new
cluster_set = []
if identifier
if cluster = self.data[:clusters][identifier]
cluster_set << cluster
else
raise Fog::AWS::RDS::NotFound.new("DBCluster #{identifier} not found")
end
else
cluster_set = self.data[:clusters].values
end
response.status = 200
response.body = {
"ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id },
"DescribeDBClustersResult" => { "DBClusters" => cluster_set }
}
response
end
end
end
end
end

View file

@ -64,6 +64,10 @@ module Fog
when "modifying"
# TODO there are some fields that only applied after rebooting
if Time.now - self.data[:modify_time] >= Fog::Mock.delay
if new_id = server["PendingModifiedValues"] && server["PendingModifiedValues"]["DBInstanceIdentifier"]
self.data[:servers][new_id] = self.data[:servers].delete(server["DBInstanceIdentifier"])
end
server.merge!(server["PendingModifiedValues"])
server["PendingModifiedValues"] = {}
server["DBInstanceStatus"] = 'available'

View file

@ -47,7 +47,7 @@ module Fog
class Mock
def modify_db_instance(db_name, apply_immediately, _options={})
options = _options
options = _options.dup
response = Excon::Response.new
if server = self.data[:servers][db_name]
if server["DBInstanceStatus"] != "available"
@ -64,6 +64,7 @@ module Fog
#end
if options["NewDBInstanceIdentifier"]
options["DBInstanceIdentifier"] = options.delete("NewDBInstanceIdentifier")
options["Endpoint"] = {"Port" => server["Endpoint"]["Port"], "Address"=> Fog::AWS::Mock.rds_address(options["DBInstanceIdentifier"],region)}
end
rds_security_groups = self.data[:security_groups].values

View file

@ -0,0 +1,54 @@
Shindo.tests("AWS::RDS | cluster", ["aws", "rds"]) do
model_tests(Fog::AWS[:rds].clusters, rds_default_cluster_params) do
@cluster_id = @instance.id
@instance.wait_for(20*60) { ready? }
@cluster_with_final_snapshot = Fog::AWS[:rds].clusters.create(rds_default_cluster_params.merge(:id => uniq_id("fog-snapshot-test"), :backup_retention_period => 1))
tests("#servers") do
returns([]) { @instance.servers }
end
@server = Fog::AWS[:rds].servers.create(rds_default_server_params.reject { |k,v| [:allocated_storage, :master_username, :password, :backup_retention_period].include?(k) }.merge(engine: "aurora", cluster_id: @instance.id, flavor_id: "db.r3.large"))
@server.wait_for(20*60) { ready? }
tests("#servers") do
@instance.reload
returns([{"DBInstanceIdentifier" => @server.id, "master" => true}]) { @instance.db_cluster_members }
returns([@server]) { @instance.servers }
end
tests("#snapshots") do
returns([]) { @instance.snapshots }
snapshot_id = uniq_id("manual-snapshot")
snapshot = @instance.snapshots.create(id: snapshot_id)
returns(snapshot_id) { snapshot.id }
snapshot.wait_for { ready? }
returns([snapshot.id]) { @instance.snapshots.map(&:id) }
snapshot.destroy
end
tests("#destroy") do
snapshot_id = uniq_id("fog-snapshot")
@instance.servers.map(&:destroy)
@cluster_with_final_snapshot.wait_for(20*60) { ready? }
@cluster_with_final_snapshot.destroy(snapshot_id)
snapshot = Fog::AWS[:rds].cluster_snapshots.get(snapshot_id)
snapshot.wait_for { ready? }
returns(snapshot_id) { snapshot.id }
snapshot.destroy
end
after do
if cluster = Fog::AWS[:rds].clusters.get(@cluster_id)
unless cluster.state = 'deleting'
cluster.servers.map(&:destroy)
cluster.destroy
end
end
end
end
end

View file

@ -0,0 +1,5 @@
Shindo.tests("AWS::RDS | clusters", ["aws", "rds"]) do
collection_tests(Fog::AWS[:rds].clusters, rds_default_cluster_params) do
@instance.wait_for { ready? }
end
end

View file

@ -10,3 +10,16 @@ def rds_default_server_params
:flavor_id => 'db.m3.medium',
}
end
def rds_default_cluster_params
{
:allocated_storage => 50,
:backup_retention_period => 10,
:engine => "aurora",
:version => "5.6.10a",
:id => uniq_id,
:master_username => "fogclusteruser",
:password => "fogpassword",
:flavor_id => "db.r3.large"
}
end

View file

@ -0,0 +1,42 @@
Shindo.tests('AWS::RDS | cluster snapshot requests', ['aws', 'rds']) do
@cluster_id = uniq_id("fog-test")
@snapshot_id = uniq_id("cluster-db-snapshot")
@cluster = Fog::AWS[:rds].clusters.create(rds_default_cluster_params.merge(id: @cluster_id))
tests("success") do
tests("#create_db_cluster_snapshot").formats(AWS::RDS::Formats::CREATE_DB_CLUSTER_SNAPSHOT) do
result = Fog::AWS[:rds].create_db_cluster_snapshot(@cluster_id, @snapshot_id).body
snapshot = result['CreateDBClusterSnapshotResult']['DBClusterSnapshot']
returns(@snapshot_id) { snapshot["DBClusterSnapshotIdentifier"] }
returns(@cluster.engine) { snapshot["Engine"] }
returns(@cluster.id) { snapshot["DBClusterIdentifier"] }
returns(@cluster.engine_version) { snapshot["EngineVersion"] }
returns(@cluster.allocated_storage) { snapshot["AllocatedStorage"].to_i }
returns(@cluster.master_username) { snapshot["MasterUsername"] }
result
end
second_snapshot = Fog::AWS[:rds].create_db_cluster_snapshot(@cluster_id, uniq_id("second-snapshot")).body['CreateDBClusterSnapshotResult']['DBClusterSnapshot']
tests("#describe_db_cluster_snapshots").formats(AWS::RDS::Formats::DESCRIBE_DB_CLUSTER_SNAPSHOTS) do
result = Fog::AWS[:rds].describe_db_cluster_snapshots.body
snapshots = result['DescribeDBClusterSnapshotsResult']['DBClusterSnapshots']
returns(2) { snapshots.count }
single_result = Fog::AWS[:rds].describe_db_cluster_snapshots(snapshot_id: second_snapshot['DBClusterSnapshotIdentifier']).body['DescribeDBClusterSnapshotsResult']['DBClusterSnapshots']
returns([second_snapshot['DBClusterSnapshotIdentifier']]) { single_result.map { |s| s['DBClusterSnapshotIdentifier'] } }
result
end
tests("delete_db_cluster_snapshot").formats(AWS::RDS::Formats::DELETE_DB_CLUSTER_SNAPSHOT) do
result = Fog::AWS[:rds].delete_db_cluster_snapshot(@snapshot_id).body
returns([]) { Fog::AWS[:rds].describe_db_cluster_snapshots(snapshot_id: @snapshot_id).body['DescribeDBClusterSnapshotsResult']['DBClusterSnapshots'] }
result
end
end
end

View file

@ -0,0 +1,37 @@
Shindo.tests('AWS::RDS | cluster requests', ['aws', 'rds']) do
suffix = rand(65535).to_s(16)
@cluster_id = "fog-test-#{suffix}"
@master_id = "fog-master-#{suffix}"
@final_snapshot_id = "fog-snapshot-#{suffix}"
tests("success") do
tests("#create_db_cluster").formats(AWS::RDS::Formats::CREATE_DB_CLUSTER) do
result = Fog::AWS[:rds].create_db_cluster(@cluster_id,
'Engine' => 'aurora',
'MasterUsername' => "fog-#{suffix}",
'MasterUserPassword' => "fog-#{suffix}",
).body
cluster = result['CreateDBClusterResult']['DBCluster']
returns("1") { cluster['AllocatedStorage'] }
returns('aurora') { cluster['Engine'] }
returns("fog-#{suffix}") { cluster['MasterUsername'] }
result
end
tests("#describe_db_clusters").formats(AWS::RDS::Formats::DESCRIBE_DB_CLUSTERS) do
Fog::AWS[:rds].describe_db_clusters.body
end
tests("#delete_db_cluster").formats(AWS::RDS::Formats::DELETE_DB_CLUSTER) do
body = Fog::AWS[:rds].delete_db_cluster(@cluster_id, @final_snapshot_id).body
tests('final snapshot') do
returns('creating') { Fog::AWS[:rds].describe_db_cluster_snapshots(:snapshot_id => @final_snapshot_id).body['DescribeDBClusterSnapshotsResult']['DBClusterSnapshots'].first['Status'] }
end
body
end
end
end

View file

@ -159,6 +159,7 @@ class AWS
'BackupRetentionPeriod' => Integer,
'CACertificateIdentifier' => String,
'CharacterSetName' => Fog::Nullable::String,
'DBClusterIndentifier' => Fog::Nullable::String,
'DbiResourceId' => Fog::Nullable::String,
'DBInstanceClass' => String,
'DBInstanceIdentifier' => String,
@ -312,6 +313,83 @@ class AWS
}
)
DB_CLUSTER = {
'AllocatedStorage' => String,
'BackupRetentionPeriod' => String,
'DBClusterIdentifier' => String,
'DBClusterMembers' => [{
"master" => Fog::Nullable::Boolean,
"DBInstanceIdentifier" => Fog::Nullable::String,
}],
'DBClusterParameterGroup' => String,
'DBSubnetGroup' => String,
'Endpoint' => String,
'Engine' => String,
'EngineVersion' => String,
'MasterUsername' => String,
'Port' => String,
'PreferredBackupWindow' => String,
'PreferredMaintenanceWindow' => String,
'Status' => String,
'VpcSecurityGroups' => [{
"VpcSecurityGroupId" => Fog::Nullable::String,
}]
}
DESCRIBE_DB_CLUSTERS = BASIC.merge({
'DescribeDBClustersResult' => {
'Marker' => Fog::Nullable::String,
'DBClusters' => [DB_CLUSTER]
}
})
CREATE_DB_CLUSTER = BASIC.merge(
'CreateDBClusterResult' => {
'DBCluster' => DB_CLUSTER
}
)
DELETE_DB_CLUSTER = BASIC.merge(
'DeleteDBClusterResult' => {
'DBCluster' => DB_CLUSTER
}
)
DB_CLUSTER_SNAPSHOT = {
'AllocatedStorage' => Fog::Nullable::Integer,
'ClusterCreateTime' => Fog::Nullable::Time,
'DBClusterIdentifier' => String,
'DBClusterSnapshotIdentifier' => String,
'Engine' => String,
'LicenseModel' => String,
'MasterUsername' => String,
'PercentProgress' => Fog::Nullable::Integer,
'Port' => Fog::Nullable::Integer,
'SnapshotCreateTime' => Fog::Nullable::Time,
'SnapshotType' => String,
'Status' => String,
'VpcId' => Fog::Nullable::String
}
CREATE_DB_CLUSTER_SNAPSHOT = BASIC.merge(
'CreateDBClusterSnapshotResult' => {
'DBClusterSnapshot' => DB_CLUSTER_SNAPSHOT
}
)
DESCRIBE_DB_CLUSTER_SNAPSHOTS = BASIC.merge(
'DescribeDBClusterSnapshotsResult' => {
'Marker' => Fog::Nullable::String,
'DBClusterSnapshots' => [DB_CLUSTER_SNAPSHOT],
}
)
DELETE_DB_CLUSTER_SNAPSHOT = BASIC.merge(
'DeleteDBClusterSnapshotResult' => {
'DBClusterSnapshot' => DB_CLUSTER_SNAPSHOT,
}
)
RESTORE_DB_INSTANCE_FROM_DB_SNAPSHOT = BASIC.merge({
'RestoreDBInstanceFromDBSnapshotResult' => {
'DBInstance' => INSTANCE