PostgreSQL high availability (#1001)

* Update sqladmin api

Pull in updates to the generated sqladmin api and update callers for
the change in the StorageAutoResize setting

* Add support for availability_type setting

Allow specifying ZONAL or REGIONAL to allow for PostgreSQL HA
setup.

* vendor: update sqladmin/v1beta4

* Test setting AvailabilityType for PostgreSQL

Add tests that cover the creation of a Postgres database with
AvailabilityType set to REGIONAL, and correct some small issues that
were preventing compilation.

* Fix breaking change w/ disk_autoresize in cloudsql

95e5582766

The cloudsql admin client changed the way it handles StorageAutoResize
as a parameter, in order to be more explicit about when the server has
ommitted the field. This changed the type from being bool to *bool, and
we need to modify provider code so that we supply the right value to the
api client.
This commit is contained in:
Lawrence Jones 2018-01-24 17:23:48 +00:00 committed by Dana Hoffman
parent 33626a1e33
commit 9f42aeb652
6 changed files with 1043 additions and 177 deletions

View File

@ -11,7 +11,7 @@ import (
"github.com/hashicorp/terraform/helper/validation"
"google.golang.org/api/googleapi"
"google.golang.org/api/sqladmin/v1beta4"
sqladmin "google.golang.org/api/sqladmin/v1beta4"
)
var sqlDatabaseAuthorizedNetWorkSchemaElem *schema.Resource = &schema.Resource{
@ -73,6 +73,16 @@ func resourceSqlDatabaseInstance() *schema.Resource {
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"availability_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: suppressFirstGen,
// Set computed instead of default because this property is for second-gen
// only. The default when not provided is ZONAL, which means no explicit HA
// configuration.
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"REGIONAL", "ZONAL"}, false),
},
"backup_configuration": &schema.Schema{
Type: schema.TypeList,
Optional: true,
@ -343,23 +353,28 @@ func resourceSqlDatabaseInstance() *schema.Resource {
}
}
// Suppress diff with any disk_autoresize value on 1st Generation Instances
// Suppress diff with any attribute value that is not supported on 1st Generation
// Instances
func suppressFirstGen(k, old, new string, d *schema.ResourceData) bool {
settingsList := d.Get("settings").([]interface{})
settings := settingsList[0].(map[string]interface{})
tier := settings["tier"].(string)
matched, err := regexp.MatchString("db*", tier)
if err != nil {
log.Printf("[ERR] error with regex in diff supression for disk_autoresize: %s", err)
}
if !matched {
log.Printf("[DEBUG] suppressing diff on disk_autoresize due to 1st gen instance type")
if isFirstGen(d) {
log.Printf("[DEBUG] suppressing diff on %s due to 1st gen instance type", k)
return true
}
return false
}
// Detects whether a database is 1st Generation by inspecting the tier name
func isFirstGen(d *schema.ResourceData) bool {
settingsList := d.Get("settings").([]interface{})
settings := settingsList[0].(map[string]interface{})
tier := settings["tier"].(string)
// 1st Generation databases have tiers like 'D0', as opposed to 2nd Generation which are
// prefixed with 'db'
return !regexp.MustCompile("db*").Match([]byte(tier))
}
func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
@ -391,6 +406,10 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
}
}
if v, ok := _settings["availability_type"]; ok {
settings.AvailabilityType = v.(string)
}
if v, ok := _settings["backup_configuration"]; ok {
_backupConfigurationList := v.([]interface{})
@ -416,7 +435,11 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
settings.CrashSafeReplicationEnabled = v.(bool)
}
settings.StorageAutoResize = _settings["disk_autoresize"].(bool)
// 1st Generation instances don't support the disk_autoresize parameter
if !isFirstGen(d) {
autoResize := _settings["disk_autoresize"].(bool)
settings.StorageAutoResize = &autoResize
}
if v, ok := _settings["disk_size"]; ok && v.(int) > 0 {
settings.DataDiskSizeGb = int64(v.(int))
@ -718,11 +741,16 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
_settingsList := _settingsListCast.([]interface{})
_settings := _settingsList[0].(map[string]interface{})
settings := &sqladmin.Settings{
Tier: _settings["tier"].(string),
SettingsVersion: instance.Settings.SettingsVersion,
StorageAutoResize: _settings["disk_autoresize"].(bool),
ForceSendFields: []string{"StorageAutoResize"},
Tier: _settings["tier"].(string),
SettingsVersion: instance.Settings.SettingsVersion,
ForceSendFields: []string{"StorageAutoResize"},
}
if !isFirstGen(d) {
autoResize := _settings["disk_autoresize"].(bool)
settings.StorageAutoResize = &autoResize
}
if v, ok := _settings["activation_policy"]; ok {
@ -737,6 +765,10 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
}
}
if v, ok := _settings["availability_type"]; ok {
settings.AvailabilityType = v.(string)
}
if v, ok := _settings["backup_configuration"]; ok {
_backupConfigurationList := v.([]interface{})
@ -984,8 +1016,8 @@ func flattenSettings(settings *sqladmin.Settings) []map[string]interface{} {
"tier": settings.Tier,
"activation_policy": settings.ActivationPolicy,
"authorized_gae_applications": settings.AuthorizedGaeApplications,
"availability_type": settings.AvailabilityType,
"crash_safe_replication": settings.CrashSafeReplicationEnabled,
"disk_autoresize": settings.StorageAutoResize,
"disk_type": settings.DataDiskType,
"disk_size": settings.DataDiskSizeGb,
"pricing_plan": settings.PricingPlan,
@ -1007,10 +1039,15 @@ func flattenSettings(settings *sqladmin.Settings) []map[string]interface{} {
if settings.LocationPreference != nil {
data["location_preference"] = flattenLocationPreference(settings.LocationPreference)
}
if settings.MaintenanceWindow != nil {
data["maintenance_window"] = flattenMaintenanceWindow(settings.MaintenanceWindow)
}
if settings.StorageAutoResize != nil {
data["disk_autoresize"] = *settings.StorageAutoResize
}
return []map[string]interface{}{data}
}

View File

@ -318,6 +318,40 @@ func TestAccGoogleSqlDatabaseInstance_slave(t *testing.T) {
})
}
func TestAccGoogleSqlDatabaseInstance_highAvailability(t *testing.T) {
t.Parallel()
var instance sqladmin.DatabaseInstance
instanceID := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccGoogleSqlDatabaseInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_highAvailability, instanceID),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlDatabaseInstanceExists(
"google_sql_database_instance.instance", &instance),
testAccCheckGoogleSqlDatabaseInstanceEquals(
"google_sql_database_instance.instance", &instance),
// Check that we've set our high availability type correctly, and it's been
// accepted by the API
func(s *terraform.State) error {
if instance.Settings.AvailabilityType != "REGIONAL" {
return fmt.Errorf("Database %s was not configured with Regional HA", instance.Name)
}
return nil
},
),
},
},
})
}
func TestAccGoogleSqlDatabaseInstance_diskspecs(t *testing.T) {
t.Parallel()
@ -403,7 +437,7 @@ func TestAccGoogleSqlDatabaseInstance_settings_upgrade(t *testing.T) {
})
}
func TestAccGoogleSqlDatabaseInstance_settings_downgrade(t *testing.T) {
func TestAccGoogleSqlDatabaseInstance_settingsDowngrade(t *testing.T) {
t.Parallel()
var instance sqladmin.DatabaseInstance
@ -542,6 +576,12 @@ func testAccCheckGoogleSqlDatabaseInstanceEquals(n string,
return fmt.Errorf("Error settings.activation_policy mismatch, (%s, %s)", server, local)
}
server = instance.Settings.AvailabilityType
local = attributes["settings.0.availability_type"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.availability_type mismatch, (%s, %s)", server, local)
}
if instance.Settings.BackupConfiguration != nil {
server = strconv.FormatBool(instance.Settings.BackupConfiguration.BinaryLogEnabled)
local = attributes["settings.0.backup_configuration.0.binary_log_enabled"]
@ -568,10 +608,15 @@ func testAccCheckGoogleSqlDatabaseInstanceEquals(n string,
return fmt.Errorf("Error settings.crash_safe_replication mismatch, (%s, %s)", server, local)
}
server = strconv.FormatBool(instance.Settings.StorageAutoResize)
local = attributes["settings.0.disk_autoresize"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.disk_autoresize mismatch, (%s, %s)", server, local)
// First generation CloudSQL instances will not have any value for StorageAutoResize.
// We need to check if this value has been omitted before we potentially deference a
// nil pointer.
if instance.Settings.StorageAutoResize != nil {
server = strconv.FormatBool(*instance.Settings.StorageAutoResize)
local = attributes["settings.0.disk_autoresize"]
if server != local && len(server) > 0 && len(local) > 0 {
return fmt.Errorf("Error settings.disk_autoresize mismatch, (%s, %s)", server, local)
}
}
server = strconv.FormatInt(instance.Settings.DataDiskSizeGb, 10)
@ -904,6 +949,25 @@ resource "google_sql_database_instance" "instance_slave" {
}
`
var testGoogleSqlDatabaseInstance_highAvailability = `
resource "google_sql_database_instance" "instance" {
name = "tf-lw-%d"
region = "us-central1"
database_version = "POSTGRES_9_6"
settings {
tier = "db-f1-micro"
availability_type = "REGIONAL"
backup_configuration {
enabled = true
binary_log_enabled = true
}
}
}
`
var testGoogleSqlDatabaseInstance_diskspecs = `
resource "google_sql_database_instance" "instance" {
name = "tf-lw-%d"

View File

@ -1,12 +1,12 @@
{
"kind": "discovery#restDescription",
"etag": "\"tbys6C40o18GZwyMen5GMkdK-3s/0q1bgr0rR6WxzzPOS6Rfltu7D84\"",
"etag": "\"YWOzh2SDasdU84ArJnpYek-OMdg/qGR3q9t_cRz9md0zXxOmhlJ1eX0\"",
"discoveryVersion": "v1",
"id": "sqladmin:v1beta4",
"name": "sqladmin",
"canonicalName": "SQL Admin",
"version": "v1beta4",
"revision": "20161115",
"revision": "20171011",
"title": "Cloud SQL Administration API",
"description": "Creates and configures Cloud SQL instances, which provide fully-managed MySQL databases.",
"ownerDomain": "google.com",
@ -21,7 +21,7 @@
"basePath": "/sql/v1beta4/",
"rootUrl": "https://www.googleapis.com/",
"servicePath": "sql/v1beta4/",
"batchPath": "batch",
"batchPath": "batch/sqladmin/v1beta4",
"parameters": {
"alt": {
"type": "string",
@ -344,7 +344,7 @@
},
"databaseVersion": {
"type": "string",
"description": "The database engine type and version. The databaseVersion can not be changed after instance creation. Can be MYSQL_5_5, MYSQL_5_6 or MYSQL_5_7. Defaults to MYSQL_5_6. MYSQL_5_7 is applicable only to Second Generation instances."
"description": "The database engine type and version. The databaseVersion field can not be changed after instance creation. MySQL Second Generation instances: MYSQL_5_7 (default) or MYSQL_5_6. PostgreSQL instances: POSTGRES_9_6 MySQL First Generation instances: MYSQL_5_6 (default) or MYSQL_5_5"
},
"etag": {
"type": "string",
@ -364,6 +364,10 @@
}
}
},
"gceZone": {
"type": "string",
"description": "The GCE zone that the instance is serving from. In case when the instance is failed over to standby zone, this value may be different with what user specified in the settings."
},
"instanceType": {
"type": "string",
"description": "The instance type. This can be one of the following.\nCLOUD_SQL_INSTANCE: A Cloud SQL instance that is not replicating from a master.\nON_PREMISES_INSTANCE: An instance running on the customer's premises.\nREAD_REPLICA_INSTANCE: A Cloud SQL instance configured as a read-replica."
@ -479,6 +483,74 @@
}
}
},
"DemoteMasterConfiguration": {
"id": "DemoteMasterConfiguration",
"type": "object",
"description": "Read-replica configuration for connecting to the on-premises master.",
"properties": {
"kind": {
"type": "string",
"description": "This is always sql#demoteMasterConfiguration.",
"default": "sql#demoteMasterConfiguration"
},
"mysqlReplicaConfiguration": {
"$ref": "DemoteMasterMySqlReplicaConfiguration",
"description": "MySQL specific configuration when replicating from a MySQL on-premises master. Replication configuration information such as the username, password, certificates, and keys are not stored in the instance metadata. The configuration information is used only to set up the replication connection and is stored by MySQL in a file named master.info in the data directory."
}
}
},
"DemoteMasterContext": {
"id": "DemoteMasterContext",
"type": "object",
"description": "Database instance demote master context.",
"properties": {
"kind": {
"type": "string",
"description": "This is always sql#demoteMasterContext.",
"default": "sql#demoteMasterContext"
},
"masterInstanceName": {
"type": "string",
"description": "The name of the instance which will act as on-premises master in the replication setup."
},
"replicaConfiguration": {
"$ref": "DemoteMasterConfiguration",
"description": "Configuration specific to read-replicas replicating from the on-premises master."
}
}
},
"DemoteMasterMySqlReplicaConfiguration": {
"id": "DemoteMasterMySqlReplicaConfiguration",
"type": "object",
"description": "Read-replica configuration specific to MySQL databases.",
"properties": {
"caCertificate": {
"type": "string",
"description": "PEM representation of the trusted CA's x509 certificate."
},
"clientCertificate": {
"type": "string",
"description": "PEM representation of the slave's x509 certificate."
},
"clientKey": {
"type": "string",
"description": "PEM representation of the slave's private key. The corresponsing public key is encoded in the client's certificate. The format of the slave's private key can be either PKCS #1 or PKCS #8."
},
"kind": {
"type": "string",
"description": "This is always sql#demoteMasterMysqlReplicaConfiguration.",
"default": "sql#demoteMasterMysqlReplicaConfiguration"
},
"password": {
"type": "string",
"description": "The password for the replication connection."
},
"username": {
"type": "string",
"description": "The username for the replication connection."
}
}
},
"ExportContext": {
"id": "ExportContext",
"type": "object",
@ -647,6 +719,10 @@
"type": "string",
"description": "The file type for the specified uri.\nSQL: The file contains SQL statements.\nCSV: The file contains CSV data."
},
"importUser": {
"type": "string",
"description": "The PostgreSQL user for this import operation. Defaults to cloudsqlsuperuser. Used only for PostgreSQL instances."
},
"kind": {
"type": "string",
"description": "This is always sql#importContext.",
@ -669,6 +745,17 @@
}
}
},
"InstancesDemoteMasterRequest": {
"id": "InstancesDemoteMasterRequest",
"type": "object",
"description": "Database demote master request.",
"properties": {
"demoteMasterContext": {
"$ref": "DemoteMasterContext",
"description": "Contains details about the demoteMaster operation."
}
}
},
"InstancesExportRequest": {
"id": "InstancesExportRequest",
"type": "object",
@ -736,6 +823,17 @@
}
}
},
"InstancesTruncateLogRequest": {
"id": "InstancesTruncateLogRequest",
"type": "object",
"description": "Instance truncate log request.",
"properties": {
"truncateLogContext": {
"$ref": "TruncateLogContext",
"description": "Contains details about the truncate log operation."
}
}
},
"IpConfiguration": {
"id": "IpConfiguration",
"type": "object",
@ -754,7 +852,7 @@
},
"requireSsl": {
"type": "boolean",
"description": "Whether the mysqld should default to 'REQUIRE X509' for users connecting over IP."
"description": "Whether SSL connections over IP should be enforced or not."
}
}
},
@ -951,8 +1049,7 @@
"description": "Name of the database instance related to this operation."
},
"targetLink": {
"type": "string",
"description": "The URI of the instance related to the operation."
"type": "string"
},
"targetProject": {
"type": "string",
@ -1088,6 +1185,10 @@
"type": "string"
}
},
"availabilityType": {
"type": "string",
"description": "Reserved for future use."
},
"backupConfiguration": {
"$ref": "BackupConfiguration",
"description": "The daily backup configuration for the instance."
@ -1153,7 +1254,12 @@
},
"storageAutoResize": {
"type": "boolean",
"description": "Configuration to increase storage size automatically. The default value is false. Applies only to Second Generation instances."
"description": "Configuration to increase storage size automatically. The default value is true. Applies only to Second Generation instances."
},
"storageAutoResizeLimit": {
"type": "string",
"description": "The maximum size to which storage capacity can be automatically increased. The default value is 0, which specifies that there is no limit. Applies only to Second Generation instances.",
"format": "int64"
},
"tier": {
"type": "string",
@ -1164,6 +1270,14 @@
"sql.instances.update"
]
}
},
"userLabels": {
"type": "object",
"description": "User-provided labels, represented as a dictionary where each label is a single key value pair.",
"additionalProperties": {
"type": "string",
"description": "An individual label entry, composed of a key and a value."
}
}
}
},
@ -1345,6 +1459,22 @@
}
}
},
"TruncateLogContext": {
"id": "TruncateLogContext",
"type": "object",
"description": "Database Instance truncate log context.",
"properties": {
"kind": {
"type": "string",
"description": "This is always sql#truncateLogContext.",
"default": "sql#truncateLogContext"
},
"logType": {
"type": "string",
"description": "The type of log to truncate. Valid values are MYSQL_GENERAL_TABLE and MYSQL_SLOW_TABLE."
}
}
},
"User": {
"id": "User",
"type": "object",
@ -1798,6 +1928,13 @@
"path": "flags",
"httpMethod": "GET",
"description": "List all available database flags for Google Cloud SQL instances.",
"parameters": {
"databaseVersion": {
"type": "string",
"description": "Database version for flag retrieval. Flags are specific to the database version.",
"location": "query"
}
},
"response": {
"$ref": "FlagsListResponse"
},
@ -1875,6 +2012,40 @@
"https://www.googleapis.com/auth/sqlservice.admin"
]
},
"demoteMaster": {
"id": "sql.instances.demoteMaster",
"path": "projects/{project}/instances/{instance}/demoteMaster",
"httpMethod": "POST",
"description": "Demotes the standalone instance to be a read replica Cloud SQL instance of an on-premises master.",
"parameters": {
"instance": {
"type": "string",
"description": "Cloud SQL instance name.",
"required": true,
"location": "path"
},
"project": {
"type": "string",
"description": "ID of the project that contains the instance.",
"required": true,
"location": "path"
}
},
"parameterOrder": [
"project",
"instance"
],
"request": {
"$ref": "InstancesDemoteMasterRequest"
},
"response": {
"$ref": "Operation"
},
"scopes": [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/sqlservice.admin"
]
},
"export": {
"id": "sql.instances.export",
"path": "projects/{project}/instances/{instance}/export",
@ -2039,6 +2210,11 @@
"httpMethod": "GET",
"description": "Lists instances under a given project in the alphabetical order of the instance name.",
"parameters": {
"filter": {
"type": "string",
"description": "An expression for filtering the results of the request, such as by name or label.",
"location": "query"
},
"maxResults": {
"type": "integer",
"description": "The maximum number of results to return per response.",
@ -2291,6 +2467,40 @@
"https://www.googleapis.com/auth/sqlservice.admin"
]
},
"truncateLog": {
"id": "sql.instances.truncateLog",
"path": "projects/{project}/instances/{instance}/truncateLog",
"httpMethod": "POST",
"description": "Truncate MySQL general and slow query log tables",
"parameters": {
"instance": {
"type": "string",
"description": "Cloud SQL instance ID. This does not include the project ID.",
"required": true,
"location": "path"
},
"project": {
"type": "string",
"description": "Project ID of the Cloud SQL project.",
"required": true,
"location": "path"
}
},
"parameterOrder": [
"project",
"instance"
],
"request": {
"$ref": "InstancesTruncateLogRequest"
},
"response": {
"$ref": "Operation"
},
"scopes": [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/sqlservice.admin"
]
},
"update": {
"id": "sql.instances.update",
"path": "projects/{project}/instances/{instance}",

File diff suppressed because it is too large Load Diff

6
vendor/vendor.json vendored
View File

@ -1393,10 +1393,10 @@
"revisionTime": "2017-07-05T20:32:08Z"
},
{
"checksumSHA1": "moKPpECJZQR/mANGD26E7Pbxn4I=",
"checksumSHA1": "HtwBgzg8+3ND5E5NqRB5i3o9nyk=",
"path": "google.golang.org/api/sqladmin/v1beta4",
"revision": "3cc2e591b550923a2c5f0ab5a803feda924d5823",
"revisionTime": "2016-11-27T23:54:21Z"
"revision": "65b0d8655182691ad23b4fac11e6f7b897d9b634",
"revisionTime": "2018-01-23T00:03:20Z"
},
{
"checksumSHA1": "siiajn/OUThTz+jYlTMXS1ji6MA=",

View File

@ -83,6 +83,9 @@ The required `settings` block supports:
* `authorized_gae_applications` - (Optional) A list of Google App Engine (GAE)
project names that are allowed to access this instance.
* `availability_type` - (Optional) This specifies whether a PostgreSQL instance
should be set up for high availability (`REGIONAL`) or single zone (`ZONAL`).
* `crash_safe_replication` - (Optional) Specific to read instances, indicates
when crash-safe replication flags are enabled.