Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: mssql_elasticpool max_size_bytes to max_size_gb #2695

Merged
merged 46 commits into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6c4a9c0
WIP: Initial check-in for max_size_gb
WodansSon Jan 16, 2019
e6a2bd3
Finished adding validation for Elasticpool max_size_gb
WodansSon Jan 17, 2019
615fd79
Added additional SKU check and updated docs
WodansSon Jan 17, 2019
8fcfb4a
Updated tests to use max_size_gb
WodansSon Jan 17, 2019
6ba0752
fix lint errors
WodansSon Jan 17, 2019
3ccc0b4
Added additional Standard and Premium size check
WodansSon Jan 18, 2019
706e7a7
Switched logic to use maps instead
WodansSon Jan 18, 2019
541ee9e
Added values to error msgs
WodansSon Jan 18, 2019
92cd03a
fix format casing
WodansSon Jan 18, 2019
8289782
Addressed PR comments and updated some func names
WodansSon Jan 18, 2019
028c5b1
Fixed lint errors and remove helper function
WodansSon Jan 18, 2019
ee38137
Fixed test issue
WodansSon Jan 19, 2019
9635e5c
Had to special case Basic tier for setting max_size_gb
WodansSon Jan 19, 2019
4b2a43f
Added Name Tier combo validation
WodansSon Jan 24, 2019
9b5352e
Updated to auto-fill tier based of name attribute
WodansSon Jan 24, 2019
0b5dd6d
Added auto-fill attributes for Tier and Family
WodansSon Jan 25, 2019
8a86194
Added Test cases for DTU and vCore autofill
WodansSon Jan 25, 2019
2c4cb79
Update for lint errors
WodansSon Jan 25, 2019
a589be8
Update tests
WodansSon Jan 25, 2019
41f04b5
Fixed test cases
WodansSon Jan 25, 2019
0507be0
Fix Lint error
WodansSon Jan 25, 2019
08522c4
Updated GetOkExists to GetOk
WodansSon Jan 25, 2019
70d8638
Revert Auto-fill functionality
WodansSon Jan 25, 2019
807aa42
Merge branch 'master' into refactor-elasticpool-maxsize
WodansSon Jan 25, 2019
dd7d968
Fixing up after merge from master
WodansSon Jan 25, 2019
eb2f5d2
Fix lint errors
WodansSon Jan 25, 2019
faf2c8e
Removed commented code
WodansSon Jan 25, 2019
39f4113
Exposed max_size_bytes, updated documentation, and added test case
WodansSon Jan 29, 2019
0ab8771
Update azurerm/resource_arm_mssql_elasticpool.go
katbyte Jan 30, 2019
f4b805c
Fixes for PR comments
WodansSon Jan 31, 2019
544b19a
Updated GetTierFromSKUName to use map
WodansSon Jan 31, 2019
347f83d
Updated error msg for clarity
WodansSon Jan 31, 2019
3421bff
Switch to using nested maps
WodansSon Jan 31, 2019
ab9c67a
Updated errors to be dynamically generated
WodansSon Feb 1, 2019
66ac53b
Added descriptions of maps
WodansSon Feb 1, 2019
98eb1d3
Unifying func signatures
WodansSon Feb 1, 2019
d397373
Removed unneeded validation check
WodansSon Feb 1, 2019
5994856
Updated name and family validation
WodansSon Feb 1, 2019
be8580b
Fixed issue due to code editor auto-complete
WodansSon Feb 1, 2019
2e3809b
Changed to Errorf
WodansSon Feb 1, 2019
6933c4b
Update for edge case
WodansSon Feb 2, 2019
0bd9ea7
Updated map to get better code reuse
WodansSon Feb 2, 2019
321139f
Missed a space
WodansSon Feb 2, 2019
56ecf29
Fix lint error
WodansSon Feb 2, 2019
857797e
broke doSKUValidation into two seperate functions
katbyte Feb 5, 2019
9d69371
fix linting
katbyte Feb 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
394 changes: 394 additions & 0 deletions azurerm/helpers/azure/elasticpool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,394 @@
package azure

import (
"fmt"
"math"
"sort"
"strings"

"github.com/hashicorp/terraform/helper/schema"
)

type skuType int

const (
DTU skuType = 0
VCore skuType = 1
)

type sku struct {
Name, Tier, Family string
Capacity int
MaxAllowedGB, MaxSizeGb, MinCapacity, MaxCapacity float64
SkuType skuType
}

// getDTUMaxGB: this map holds all of the DTU to 'max_size_gb' mappings based on a DTU lookup
// note that the value can be below the returned value, except for 'basic' it's
// value must match exactly what is returned else it will be rejected by the API
// which will return a 'Internal Server Error'

var getDTUMaxGB = map[string]map[int]float64{
"basic": {
50: 4.8828125,
100: 9.765625,
200: 19.53125,
300: 29.296875,
400: 39.0625,
800: 78.125,
1200: 117.1875,
1600: 156.25,
},
"standard": {
50: 500,
100: 750,
200: 1024,
300: 1280,
400: 1536,
800: 2048,
1200: 2560,
1600: 3072,
2000: 3584,
2500: 4096,
3000: 4096,
},
"premium": {
125: 1024,
250: 1024,
500: 1024,
1000: 1024,
1500: 1536,
2000: 2048,
2500: 2560,
3000: 3072,
3500: 3584,
4000: 4096,
},
}

// supportedDTUMaxGBValues: this map holds all of the valid 'max_size_gb' values
// for a DTU SKU type. If the 'max_size_gb' is anything
// other than the values in the map the API with throw
// an 'Internal Server Error'

var supportedDTUMaxGBValues = map[int]float64{
50: 1,
100: 1,
150: 1,
200: 1,
250: 1,
300: 1,
400: 1,
500: 1,
750: 1,
800: 1,
1024: 1,
1200: 1,
1280: 1,
1536: 1,
1600: 1,
1792: 1,
2000: 1,
2048: 1,
2304: 1,
2500: 1,
2560: 1,
2816: 1,
3000: 1,
3072: 1,
3328: 1,
3584: 1,
3840: 1,
4096: 1,
}

// getvCoreMaxGB: this map holds all of the vCore to 'max_size_gb' mappings based on a vCore lookup
// note that the value can be below the returned value

var getvCoreMaxGB = map[string]map[string]map[int]float64{
"generalpurpose": {
"gen4": {
1: 512,
2: 756,
3: 1536,
4: 1536,
5: 1536,
6: 2048,
7: 2048,
8: 2048,
9: 2048,
10: 2048,
16: 3584,
24: 4096,
},
"gen5": {
2: 512,
4: 756,
6: 1536,
8: 1536,
10: 1536,
12: 2048,
14: 2048,
16: 2048,
18: 3072,
20: 3072,
24: 3072,
32: 4096,
40: 4096,
80: 4096,
},
},
"businesscritical": {
"gen4": {
2: 1024,
3: 1024,
4: 1024,
5: 1024,
6: 1024,
7: 1024,
8: 1024,
9: 1024,
10: 1024,
16: 1024,
24: 1024,
},
"gen5": {
4: 1024,
6: 1536,
8: 1536,
10: 1536,
12: 3072,
14: 3072,
16: 3072,
18: 3072,
20: 3072,
24: 4096,
32: 4096,
40: 4096,
80: 4096,
},
},
}

// getTierFromName: this map contains all of the valid mappings between 'name' and 'tier'
// the reason for this map is that the user may pass in an invalid mapping
// (e.g. name: "Basicpool" tier:"BusinessCritical") this map allows me
// to lookup the correct values in other maps even if the config file
// contains an invalid 'tier' attribute.

var getTierFromName = map[string]string{
"basicpool": "Basic",
"standardpool": "Standard",
"premiumpool": "Premium",
"gp_gen4": "GeneralPurpose",
"gp_gen5": "GeneralPurpose",
"bc_gen4": "BusinessCritical",
"bc_gen5": "BusinessCritical",
}

func MSSQLElasticPoolValidateSKU(diff *schema.ResourceDiff) error {

name := diff.Get("sku.0.name")
tier := diff.Get("sku.0.tier")
capacity := diff.Get("sku.0.capacity")
family := diff.Get("sku.0.family")
maxSizeBytes := diff.Get("max_size_bytes")
maxSizeGb := diff.Get("max_size_gb")
minCapacity := diff.Get("per_database_settings.0.min_capacity")
maxCapacity := diff.Get("per_database_settings.0.max_capacity")

s := sku{
Name: name.(string),
Tier: tier.(string),
Family: family.(string),
Capacity: capacity.(int),
MaxSizeGb: maxSizeGb.(float64),
MinCapacity: minCapacity.(float64),
MaxCapacity: maxCapacity.(float64),
SkuType: DTU,
}

// Convert Bytes to Gigabytes only if
// 'max_size_bytes' has changed
if diff.HasChange("max_size_bytes") {
s.MaxSizeGb = float64(maxSizeBytes.(int) / 1024 / 1024 / 1024)
}

// Check to see if the name describes a vCore type SKU
if strings.HasPrefix(strings.ToLower(s.Name), "gp_") || strings.HasPrefix(strings.ToLower(s.Name), "bc_") {
s.SkuType = VCore
}

// Universal check for both DTU and vCore based SKUs
if !nameTierIsValid(s) {
return fmt.Errorf("Mismatch between SKU name '%s' and tier '%s', expected 'tier' to be '%s'", s.Name, s.Tier, getTierFromName[strings.ToLower(s.Name)])
}

// Verify that Family is valid
if s.SkuType == DTU && s.Family != "" {
return fmt.Errorf("Invalid attribute 'family'(%s) for service tier '%s', remove the 'family' attribute from the configuration file", s.Family, s.Tier)
} else if s.SkuType == VCore && !nameContainsFamily(s) {
return fmt.Errorf("Mismatch between SKU name '%s' and family '%s', expected '%s'", s.Name, s.Family, getFamilyFromName(s))
}

//get max GB and do validation based on SKU type
if s.SkuType == DTU {
s.MaxAllowedGB = getDTUMaxGB[strings.ToLower(s.Tier)][s.Capacity]
return doDTUSKUValidation(s)
} else {
s.MaxAllowedGB = getvCoreMaxGB[strings.ToLower(s.Tier)][strings.ToLower(s.Family)][s.Capacity]
return doVCoreSKUValidation(s)
}

return nil
}

func nameContainsFamily(s sku) bool {
if s.Family == "" {
return false
}

return strings.Contains(strings.ToLower(s.Name), strings.ToLower(s.Family))
}

func nameTierIsValid(s sku) bool {
if strings.EqualFold(s.Name, "BasicPool") && !strings.EqualFold(s.Tier, "Basic") ||
strings.EqualFold(s.Name, "StandardPool") && !strings.EqualFold(s.Tier, "Standard") ||
strings.EqualFold(s.Name, "PremiumPool") && !strings.EqualFold(s.Tier, "Premium") ||
strings.HasPrefix(strings.ToLower(s.Name), "gp_") && !strings.EqualFold(s.Tier, "GeneralPurpose") ||
strings.HasPrefix(strings.ToLower(s.Name), "bc_") && !strings.EqualFold(s.Tier, "BusinessCritical") {
return false
}

return true
}

func getFamilyFromName(s sku) string {
if !strings.HasPrefix(strings.ToLower(s.Name), "gp_") && !strings.HasPrefix(strings.ToLower(s.Name), "bc_") {
return ""
}

nameFamily := s.Name[3:]
retFamily := "Gen4" // Default

if strings.EqualFold(nameFamily, "Gen5") {
retFamily = "Gen5"
}

return retFamily
}

func getDTUCapacityErrorMsg(s sku) string {
m := getDTUMaxGB[strings.ToLower(s.Tier)]
stub := fmt.Sprintf("service tier '%s' must have a 'capacity'(%d) of ", s.Tier, s.Capacity)
return buildErrorString(stub, m) + " DTUs"
}

func getVCoreCapacityErrorMsg(s sku) string {
m := getvCoreMaxGB[strings.ToLower(s.Tier)][strings.ToLower(s.Family)]
stub := fmt.Sprintf("service tier '%s' %s must have a 'capacity'(%d) of ", s.Tier, s.Family, s.Capacity)
return buildErrorString(stub, m) + " vCores"
}

func getDTUNotValidSizeErrorMsg(s sku) string {
m := supportedDTUMaxGBValues
stub := fmt.Sprintf("'max_size_gb'(%d) is not a valid value for service tier '%s', 'max_size_gb' must have a value of ", int(s.MaxSizeGb), s.Tier)
return buildErrorString(stub, m) + " GB"
}

func buildErrorString(stub string, m map[int]float64) string {
var a []int

// copy the keys into another map
p := make([]int, 0, len(m))
for k := range m {
p = append(p, k)
}

// copy the values of the map of keys into a slice of ints
for v := range p {
a = append(a, p[v])
}

// sort the slice to get them in order
sort.Ints(a)

// build the error message
for i := range a {
if i < len(a)-1 {
stub += fmt.Sprintf("%d, ", a[i])
} else {
stub += fmt.Sprintf("or %d", a[i])
}
}

return stub
}

func doDTUSKUValidation(s sku) error {

if s.MaxAllowedGB == 0 {
return fmt.Errorf(getDTUCapacityErrorMsg(s))
}

if strings.EqualFold(s.Name, "BasicPool") {
// Basic SKU does not let you pick your max_size_GB they are fixed values
if s.MaxSizeGb != s.MaxAllowedGB {
return fmt.Errorf("service tier 'Basic' with a 'capacity' of %d must have a 'max_size_gb' of %.7f GB, got %.7f GB", s.Capacity, s.MaxAllowedGB, s.MaxSizeGb)
}
} else {
// All other DTU based SKUs
if s.MaxSizeGb > s.MaxAllowedGB {
return fmt.Errorf("service tier '%s' with a 'capacity' of %d must have a 'max_size_gb' no greater than %d GB, got %d GB", s.Tier, s.Capacity, int(s.MaxAllowedGB), int(s.MaxSizeGb))
}

if int(s.MaxSizeGb) < 50 {
return fmt.Errorf("service tier '%s', must have a 'max_size_gb' value equal to or greater than 50 GB, got %d GB", s.Tier, int(s.MaxSizeGb))
}

// Check to see if the max_size_gb value is valid for this SKU type and capacity
if supportedDTUMaxGBValues[int(s.MaxSizeGb)] != 1 {
return fmt.Errorf(getDTUNotValidSizeErrorMsg(s))
}
}

// All Other DTU based SKU Checks
if s.MinCapacity != math.Trunc(s.MinCapacity) {
return fmt.Errorf("service tier '%s' must have whole numbers as their 'minCapacity'", s.Tier)
}

if s.MaxCapacity != math.Trunc(s.MaxCapacity) {
return fmt.Errorf("service tier '%s' must have whole numbers as their 'maxCapacity'", s.Tier)
}

return nil
}

func doVCoreSKUValidation(s sku) error {

if s.MaxAllowedGB == 0 {
return fmt.Errorf(getVCoreCapacityErrorMsg(s))
}

if s.MaxSizeGb > s.MaxAllowedGB {
return fmt.Errorf("service tier '%s' %s with a 'capacity' of %d vCores must have a 'max_size_gb' between 5 GB and %d GB, got %d GB", s.Tier, s.Family, s.Capacity, int(s.MaxAllowedGB), int(s.MaxSizeGb))
}

if int(s.MaxSizeGb) < 5 {
return fmt.Errorf("service tier '%s' must have a 'max_size_gb' value equal to or greater than 5 GB, got %d GB", s.Tier, int(s.MaxSizeGb))
}

if s.MaxSizeGb != math.Trunc(s.MaxSizeGb) {
return fmt.Errorf("'max_size_gb' must be a whole number, got %f GB", s.MaxSizeGb)
}

if s.MaxCapacity > float64(s.Capacity) {
return fmt.Errorf("service tier '%s' perDatabaseSettings 'maxCapacity'(%d) must not be higher than the SKUs 'capacity'(%d) value", s.Tier, int(s.MaxCapacity), s.Capacity)
}

if s.MinCapacity > s.MaxCapacity {
return fmt.Errorf("perDatabaseSettings 'maxCapacity'(%d) must be greater than or equal to the perDatabaseSettings 'minCapacity'(%d) value", int(s.MaxCapacity), int(s.MinCapacity))
}

return nil
}