|
| 1 | +#r "Newtonsoft.Json" |
| 2 | +#r "System.Runtime.Serialization" |
| 3 | + |
| 4 | +//************************************************************* |
| 5 | +// Azure Database for MySQL/PostgreSQL Scaling Down/Up Functions |
| 6 | +// |
| 7 | +// For REST API params, refer to |
| 8 | +// https://docs.microsoft.com/en-us/rest/api/mysql/servers/update |
| 9 | +//************************************************************** |
| 10 | + |
| 11 | +using System; |
| 12 | +using Newtonsoft.Json; |
| 13 | +using Newtonsoft.Json.Linq; |
| 14 | +using System.Net.Http; |
| 15 | +using Microsoft.Rest.Azure.Authentication; |
| 16 | +using Microsoft.Azure.Management.ResourceManager; |
| 17 | +using Microsoft.Azure.Management.ResourceManager.Models; |
| 18 | + |
| 19 | +//private const string _MySQL_Namespace = "Microsoft.DBforMySQL"; |
| 20 | +//private const string _PostgreSQL_Namespace = "Microsoft.DBforPostgreSQL"; |
| 21 | +private const string _apiVersion = "2017-12-01"; |
| 22 | +private const string _dbTier_basic = "Basic"; |
| 23 | +private const string _dbTier_generalPurpose = "GeneralPurpose"; |
| 24 | +private const string _dbTier_memoryOptimized = "MemoryOptimized"; |
| 25 | +private static int[] _scaleModel_basic = {1,2}; |
| 26 | +private static int[] _scaleModel_generalPurpose = { 2, 4, 8, 16, 32 }; |
| 27 | +private static int[] _scaleModel_memoryOptimized = { 2, 4, 8, 16, 32 }; |
| 28 | + |
| 29 | +private static readonly string _tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID"); |
| 30 | +private static readonly string _clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); |
| 31 | +private static readonly string _clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET"); |
| 32 | +private static readonly string _subscriptionId = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID"); |
| 33 | +private static readonly string _dbAdminUser = Environment.GetEnvironmentVariable("DB_ADMIN_USER"); |
| 34 | +private static readonly string _dbAdminPassword = Environment.GetEnvironmentVariable("DB_ADMIN_PASSWORD"); |
| 35 | + |
| 36 | +private static HttpClient client = new HttpClient(); |
| 37 | + |
| 38 | +public static void Run(string myQueueItem, TraceWriter log) |
| 39 | +{ |
| 40 | + log.Info($"queueTrigger Function was triggerd"); |
| 41 | + string jsonContent = myQueueItem; |
| 42 | + dynamic data = JsonConvert.DeserializeObject(jsonContent); |
| 43 | + log.Info("Request : " + jsonContent); |
| 44 | + string callbackUrl = data.CallbackUrl; |
| 45 | + |
| 46 | + dynamic context = JsonConvert.DeserializeObject((string)data.Context); |
| 47 | + string resourceId = (string) context["resourceId"]; |
| 48 | + string resourceRegion = (string) context["resourceRegion"]; |
| 49 | + dynamic condition = context["condition"].ToObject<Dictionary<string, string>>(); |
| 50 | + string alertOperator = condition["operator"]; |
| 51 | + log.Info("Input - callbackUrl : " + callbackUrl); |
| 52 | + log.Info("Input - resourceId : " + resourceId); |
| 53 | + log.Info("Input - resourceRegion : " + resourceRegion); |
| 54 | + log.Info("Input - alertOperator : " + alertOperator); |
| 55 | + |
| 56 | + string[] restokens = resourceId.Split('/'); |
| 57 | + if (restokens.Length != 9 ) { |
| 58 | + var errmsg=string.Format("Invalid resource Id: {0}", resourceId); |
| 59 | + log.Info(errmsg); |
| 60 | + throw new Exception(errmsg); |
| 61 | + } |
| 62 | + |
| 63 | + string dbResourceGroup = restokens[4]; |
| 64 | + string resourceProviderNamespace=restokens[6]; |
| 65 | + string dbName = restokens[8]; |
| 66 | + log.Info(string.Format("Input - dbResourceGroup:{0} resourceProviderNamespace:{1} dbName:{2}", |
| 67 | + dbResourceGroup, |
| 68 | + resourceProviderNamespace, |
| 69 | + dbName )); |
| 70 | + |
| 71 | + if (new List<string> { |
| 72 | + _tenantId, |
| 73 | + _clientId, |
| 74 | + _clientSecret, |
| 75 | + _subscriptionId, |
| 76 | + _dbAdminUser, |
| 77 | + _dbAdminPassword |
| 78 | + }.Any(i => String.IsNullOrEmpty(i))) |
| 79 | + { |
| 80 | + string errmsg="[ERROR] Please provide ENV vars for AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_SECRET, AZURE_SUBSCRIPTION_ID, DB_ADMIN_USER, and DB_ADMIN_PASSWORD"; |
| 81 | + Console.WriteLine(errmsg); |
| 82 | + throw new Exception(errmsg); |
| 83 | + } |
| 84 | + |
| 85 | + try { |
| 86 | + // Db vCore Scale Change |
| 87 | + ScaleDatabaeVCoreCapacity( |
| 88 | + resourceRegion, |
| 89 | + dbResourceGroup, |
| 90 | + resourceProviderNamespace, |
| 91 | + dbName, |
| 92 | + alertOperator, |
| 93 | + log |
| 94 | + ).Wait(); |
| 95 | + } |
| 96 | + catch (Exception ex) |
| 97 | + { |
| 98 | + log.Info("[Exception] " + ex); |
| 99 | + throw new Exception("[Exception] " + ex); |
| 100 | + } |
| 101 | + |
| 102 | + string result = jsonContent; |
| 103 | + client.PostAsJsonAsync<string>(callbackUrl, result); |
| 104 | +} |
| 105 | + |
| 106 | + |
| 107 | +public static async Task ScaleDatabaeVCoreCapacity( |
| 108 | + string region, |
| 109 | + string resourceGroupName, |
| 110 | + string resourceProviderNamespace, |
| 111 | + string resourceName, |
| 112 | + string alertOperator, |
| 113 | + TraceWriter log |
| 114 | + ) |
| 115 | +{ |
| 116 | + |
| 117 | + // Build the service credentials and Azure Resource Manager clients |
| 118 | + var serviceCreds = |
| 119 | + await ApplicationTokenProvider.LoginSilentAsync(_tenantId, _clientId, _clientSecret); |
| 120 | + var resourceClient = |
| 121 | + new ResourceManagementClient(serviceCreds); |
| 122 | + resourceClient.SubscriptionId = _subscriptionId; |
| 123 | + |
| 124 | + //*************************************** |
| 125 | + // Get current SKU & Properties |
| 126 | + //*************************************** |
| 127 | + GenericResource gr; |
| 128 | + gr = resourceClient.Resources.Get( |
| 129 | + resourceGroupName, |
| 130 | + resourceProviderNamespace, // Microsoft.DBforMySQL or Microsoft.DBforPostgreSQL |
| 131 | + "", // fixed |
| 132 | + "servers", // fixed "servers" |
| 133 | + resourceName, // db account name |
| 134 | + _apiVersion // API version fixed |
| 135 | + ); |
| 136 | + Sku cursku = gr.Sku; |
| 137 | + log.Info( string.Format("Current SKU: name:{0}, Tier:{1}, Family:{2}, Capacity:{3}", |
| 138 | + cursku.Name, cursku.Tier, cursku.Family, cursku.Capacity)); |
| 139 | + |
| 140 | + JObject curProperties_jobject = (JObject)gr.Properties; |
| 141 | + Dictionary<string, object> newProperties = curProperties_jobject.ToObject<Dictionary<string, object>>(); |
| 142 | + Sku newsku = cursku; |
| 143 | + |
| 144 | + //*************************************** |
| 145 | + // Decide Capacity |
| 146 | + //*************************************** |
| 147 | + int[] scaleModel; |
| 148 | + string tierAcronym; |
| 149 | + switch (cursku.Tier) |
| 150 | + { |
| 151 | + case _dbTier_basic: |
| 152 | + log.Info("Basic Tier"); |
| 153 | + scaleModel = _scaleModel_basic; |
| 154 | + tierAcronym = "B"; |
| 155 | + break; |
| 156 | + case _dbTier_generalPurpose: |
| 157 | + log.Info("GeneralPurpose Tier"); |
| 158 | + scaleModel = _scaleModel_generalPurpose; |
| 159 | + tierAcronym = "GP"; |
| 160 | + break; |
| 161 | + case _dbTier_memoryOptimized: |
| 162 | + log.Info("MemoryOptimized Tier"); |
| 163 | + scaleModel = _scaleModel_memoryOptimized; |
| 164 | + tierAcronym = "MO"; |
| 165 | + break; |
| 166 | + default: |
| 167 | + log.Info("[Warning] Unknown Tier, then skip the rest of operation"); |
| 168 | + return; |
| 169 | + } |
| 170 | + |
| 171 | + int direction = 0; |
| 172 | + if ( alertOperator.Equals("GreaterThan") || alertOperator.Equals("GreaterThanOrEqual")) |
| 173 | + { |
| 174 | + direction = 1; |
| 175 | + } else if (alertOperator.Equals("LessThan") || alertOperator.Equals("LessThanOrEqual") ) |
| 176 | + { |
| 177 | + direction = -1; |
| 178 | + } |
| 179 | + int curIndex = 0; |
| 180 | + int newCapacity = (int)cursku.Capacity; // by default |
| 181 | + foreach (int c in scaleModel) |
| 182 | + { |
| 183 | + if (c == (int)cursku.Capacity) |
| 184 | + { |
| 185 | + if (direction > 0 && (scaleModel.Length - 1) > curIndex) |
| 186 | + { |
| 187 | + newCapacity = scaleModel[curIndex + 1]; |
| 188 | + break; |
| 189 | + } |
| 190 | + if (direction < 0 && 0 < curIndex) |
| 191 | + { |
| 192 | + newCapacity = scaleModel[curIndex - 1]; |
| 193 | + break; |
| 194 | + } |
| 195 | + } |
| 196 | + curIndex++; |
| 197 | + } |
| 198 | + |
| 199 | + //*************************************** |
| 200 | + // Update Sku |
| 201 | + //*************************************** |
| 202 | + if (newCapacity == (int)cursku.Capacity) |
| 203 | + { |
| 204 | + log.Info( |
| 205 | + string.Format( |
| 206 | + "Skip Database vCore Update as it has reached max or min number: Name: {0}", |
| 207 | + resourceName) ); |
| 208 | + return; |
| 209 | + } |
| 210 | + newsku.Capacity = newCapacity; |
| 211 | + newsku.Name = string.Format("{0}_{1}_{2}", tierAcronym, newsku.Family, newCapacity); |
| 212 | + log.Info( |
| 213 | + string.Format("New SKU: name:{0}, Tier:{1}, Family:{2}, Capacity:{3}", |
| 214 | + newsku.Name, newsku.Tier, newsku.Family, newsku.Capacity)); |
| 215 | + |
| 216 | + // AdminLoginPassword need to be added anytime of updating db resource |
| 217 | + // since it isn't loaded in properties object |
| 218 | + if (!newProperties.ContainsKey("administratorLoginPassword")) |
| 219 | + { |
| 220 | + newProperties.Add("administratorLoginPassword", _dbAdminPassword); |
| 221 | + newProperties["administratorLogin"] = _dbAdminUser; //Set dbAdminuser together with the password for its consistency |
| 222 | + } |
| 223 | + var databaseParams = new GenericResource |
| 224 | + { |
| 225 | + Location = region, |
| 226 | + Sku = newsku, |
| 227 | + Properties = newProperties |
| 228 | + }; |
| 229 | + var database = resourceClient.Resources.CreateOrUpdate( |
| 230 | + resourceGroupName, |
| 231 | + resourceProviderNamespace, // Microsoft.DBforMySQL or Microsoft.DBforPostgreSQL |
| 232 | + "", // fixed |
| 233 | + "servers", // fixed |
| 234 | + resourceName, // db account name |
| 235 | + _apiVersion, // API Version fixed |
| 236 | + databaseParams ); // parameters |
| 237 | + |
| 238 | + log.Info( |
| 239 | + string.Format("Database vCore scaling completed successfully: Name: {0} and Id: {1}", |
| 240 | + database.Name, database.Id)); |
| 241 | + |
| 242 | +} |
0 commit comments