-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
1,389 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 14 | ||
VisualStudioVersion = 14.0.25420.1 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlDwAutoScaler", "SqlDwAutoScaler\SqlDwAutoScaler.csproj", "{AAD1DCBD-D892-4B9E-976C-B8A53C8A1746}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{AAD1DCBD-D892-4B9E-976C-B8A53C8A1746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{AAD1DCBD-D892-4B9E-976C-B8A53C8A1746}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{AAD1DCBD-D892-4B9E-976C-B8A53C8A1746}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{AAD1DCBD-D892-4B9E-976C-B8A53C8A1746}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
|
||
bin | ||
obj | ||
csx | ||
.vs | ||
edge | ||
Publish | ||
.vscode | ||
|
||
*.user | ||
*.suo | ||
*.cscfg | ||
*.Cache | ||
project.lock.json | ||
|
||
/packages | ||
/TestResults | ||
|
||
/tools/NuGet.exe | ||
/App_Data | ||
/secrets | ||
/data | ||
.secrets |
183 changes: 183 additions & 0 deletions
183
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDw/ScaleSqlDw.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
using System; | ||
using System.Configuration; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using Newtonsoft.Json; | ||
using Microsoft.WindowsAzure.Storage.Table; | ||
using Microsoft.Azure.WebJobs.Host; | ||
using SqlDwAutoScaler.Shared; | ||
|
||
namespace SqlDwAutoScaler | ||
{ | ||
public class ScaleSqlDw | ||
{ | ||
private static TraceWriter _logger; | ||
|
||
public static async Task<object> Run(HttpRequestMessage req, TraceWriter log) | ||
{ | ||
log.Info($"ScaleSqlDW triggered!"); | ||
DwScaleLogEntity logEntity = null; | ||
CloudTable dwuScaleLogsTable = null; | ||
_logger = log; | ||
|
||
try | ||
{ | ||
var storageConnStr = ConfigurationManager.AppSettings["AzureWebJobsStorage"]; | ||
var dwLocation = ConfigurationManager.AppSettings["SqlDwLocation"]; | ||
var tableName = ConfigurationManager.AppSettings["DwScaleLogsTable"]; | ||
var dwuConfigFile = ConfigurationManager.AppSettings["DwuConfigFile"]; | ||
var dwuConfigManager = new DwuConfigManager(dwuConfigFile); | ||
|
||
string jsonContent = await req.Content.ReadAsStringAsync(); | ||
dynamic alert = JsonConvert.DeserializeObject(jsonContent); | ||
|
||
if (alert == null || alert.status == null || alert.context == null) | ||
{ | ||
return req.CreateResponse(HttpStatusCode.BadRequest, new | ||
{ | ||
error = "Request didn't have required data in it!" | ||
}); | ||
} | ||
|
||
// The function will be called both when the alert is Activated (that is, triggered) and when it is Resolved. | ||
// We only respond to Activated alert | ||
if (alert.status != "Activated") | ||
{ | ||
var message = $"Alert status is not activated! No scaling triggered!"; | ||
log.Info(message); | ||
return req.CreateResponse(HttpStatusCode.OK, new | ||
{ | ||
status = message | ||
}); | ||
} | ||
|
||
string alertName = alert.context.name; | ||
// Resource name in the alert looks like this: edudatawh/educationdatawh | ||
string dwName = alert.context.resourceName.ToString().Split('/')[1]; | ||
string alertTimeStamp = alert.context.timestamp.ToString("yyyy-MM-ddTHH:mm:ssZ"); | ||
|
||
// Get or create DW Scale logs table | ||
log.Info($"Get or create {tableName} table if it doesn't exist"); | ||
dwuScaleLogsTable = TableClientFactory.CreateTableIfNotExists(storageConnStr, tableName); | ||
|
||
// Create log entity | ||
logEntity = new DwScaleLogEntity(dwName, alertTimeStamp) | ||
{ | ||
AlertName = alertName, | ||
AlertCondition = alert.context.condition.ToString() | ||
}; | ||
|
||
// Create a DataWarehouseManagementClient | ||
var dwClient = DwClientFactory.Create(alert.context.resourceId.ToString()); | ||
// Get database information | ||
var dbInfo = dwClient.GetDatabase(); | ||
dynamic dbInfoObject = JsonConvert.DeserializeObject(dbInfo); | ||
var currentDwu = dbInfoObject.properties.requestedServiceObjectiveName.ToString(); | ||
logEntity.DwuBefore = currentDwu; | ||
log.Info($"Current DWU is {currentDwu}"); | ||
|
||
if (alertName.IndexOf("scale up", StringComparison.OrdinalIgnoreCase) >= 0) | ||
{ | ||
var upLevelDwu = dwuConfigManager.GetUpLevelDwu(currentDwu); | ||
if (upLevelDwu != currentDwu) | ||
{ | ||
log.Info($"scale up to {upLevelDwu}"); | ||
logEntity.Action = "Scale Up"; | ||
dwClient.ScaleWarehouse(upLevelDwu, dwLocation); | ||
} | ||
else | ||
{ | ||
log.Info($"Can't scale up. It's at MAX level {currentDwu} already"); | ||
} | ||
|
||
logEntity.DwuAfter = upLevelDwu; | ||
} | ||
else if (alertName.IndexOf("scale down", StringComparison.OrdinalIgnoreCase) >= 0) | ||
{ | ||
if (IsInsideScaleUpScheduleTime()) | ||
{ | ||
var message = $"Can't scale down. It's inside scheduled scale up hours"; | ||
logEntity.Error = message; | ||
log.Info(message); | ||
} | ||
else | ||
{ | ||
var downLevelDwu = dwuConfigManager.GetDownLevelDwu(currentDwu); | ||
if (downLevelDwu != currentDwu) | ||
{ | ||
log.Info($"scale down to {downLevelDwu}"); | ||
logEntity.Action = "Scale Down"; | ||
dwClient.ScaleWarehouse(downLevelDwu, dwLocation); | ||
} | ||
else | ||
{ | ||
log.Info($"Can't scale down. It's at MIN level {currentDwu} already"); | ||
} | ||
|
||
logEntity.DwuAfter = downLevelDwu; | ||
} | ||
} | ||
|
||
log.Info($"Insert log entity to DwScaleLogs table"); | ||
TableOperation insertOperation = TableOperation.Insert(logEntity); | ||
dwuScaleLogsTable.Execute(insertOperation); | ||
|
||
return req.CreateResponse(HttpStatusCode.OK, new | ||
{ | ||
status = $"Done!" | ||
}); | ||
} | ||
catch (Exception e) | ||
{ | ||
log.Info($"ScaleSqlDW threw exception: {e.Message}"); | ||
if (logEntity != null && dwuScaleLogsTable != null) | ||
{ | ||
logEntity.Error = e.Message; | ||
TableOperation insertOperation = TableOperation.Insert(logEntity); | ||
dwuScaleLogsTable.Execute(insertOperation); | ||
} | ||
|
||
return req.CreateResponse(HttpStatusCode.InternalServerError, new | ||
{ | ||
error = $"{e.Message}" | ||
}); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// To determine if current time is within the scheduled scale up hours | ||
/// </summary> | ||
/// <returns>true if current time is within the scheduled scale up hours</returns> | ||
public static bool IsInsideScaleUpScheduleTime() | ||
{ | ||
var scheduleStartTimeString = ConfigurationManager.AppSettings["ScaleUpScheduleStartTime"]; | ||
var scheduleEndTimeString = ConfigurationManager.AppSettings["ScaleUpScheduleEndTime"]; | ||
|
||
// If they are not found in app settings, return false | ||
if (string.IsNullOrEmpty(scheduleStartTimeString) || string.IsNullOrEmpty(scheduleEndTimeString)) | ||
{ | ||
return false; | ||
} | ||
|
||
string[] startTime = scheduleStartTimeString.Split(':'); | ||
string[] endTime = scheduleEndTimeString.Split(':'); | ||
|
||
// This is the time in Azure relative to the WEBSITE_TIME_ZONE setting | ||
var current = DateTime.Now; | ||
var scheduleStartTime = new DateTime(current.Year, current.Month, current.Day, Convert.ToInt32(startTime[0]), Convert.ToInt32(startTime[1]), Convert.ToInt32(startTime[2]), DateTimeKind.Utc); | ||
var scheduleEndTime = new DateTime(current.Year, current.Month, current.Day, Convert.ToInt32(endTime[0]), Convert.ToInt32(endTime[1]), Convert.ToInt32(endTime[2]), DateTimeKind.Utc); | ||
|
||
_logger.Info($"Scale up schedule start time is {scheduleStartTime}"); | ||
_logger.Info($"Scale up schedule end time is {scheduleEndTime}"); | ||
_logger.Info($"Current time is {current}"); | ||
|
||
// If current time is between schedule start time and schedule end time | ||
if (DateTime.Compare(current, scheduleStartTime) >= 0 && DateTime.Compare(current, scheduleEndTime) <= 0) | ||
{ | ||
return true; | ||
} | ||
return false; | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDw/function.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"disabled": false, | ||
"bindings": [ | ||
{ | ||
"authLevel": "function", | ||
"name": "req", | ||
"type": "httpTrigger", | ||
"direction": "in" | ||
}, | ||
{ | ||
"name": "$return", | ||
"type": "http", | ||
"direction": "out" | ||
} | ||
], | ||
"scriptFile": "..\\bin\\SqlDwAutoScaler.dll", | ||
"entryPoint": "SqlDwAutoScaler.ScaleSqlDw.Run" | ||
} |
11 changes: 11 additions & 0 deletions
11
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDw/readme.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# HttpTrigger - C<span>#</span> | ||
|
||
The `HttpTrigger` makes it incredibly easy to have your functions executed via an HTTP call to your function. | ||
|
||
## How it works | ||
|
||
When you call the function, be sure you checkout which security rules you apply. If you're using an apikey, you'll need to include that in your request. | ||
|
||
## Learn more | ||
|
||
<TODO> Documentation |
3 changes: 3 additions & 0 deletions
3
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDw/sample.dat
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"name": "Azure" | ||
} |
52 changes: 52 additions & 0 deletions
52
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDwByTimer/ScaleSqlDwByTimer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using System; | ||
using System.Configuration; | ||
using Microsoft.Azure.WebJobs.Host; | ||
using Newtonsoft.Json; | ||
using SqlDwAutoScaler.Shared; | ||
using Microsoft.Azure.WebJobs; | ||
|
||
namespace SqlDwAutoScaler | ||
{ | ||
public class ScaleSqlDwByTimer | ||
{ | ||
public static void Run(TimerInfo myTimer, TraceWriter log) | ||
{ | ||
log.Info($"ScaleSqlDwByTimer triggered!"); | ||
|
||
try | ||
{ | ||
var sqlServer = ConfigurationManager.AppSettings["SqlServerName"]; | ||
var sqlDw = ConfigurationManager.AppSettings["SqlDwName"]; | ||
var subscriptionId = ConfigurationManager.AppSettings["SubscriptionId"]; | ||
var resourceGroup = ConfigurationManager.AppSettings["SqlDwResourceGroup"]; | ||
var resourceId = $"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Sql/servers/{sqlServer}/databases/{sqlDw}"; | ||
var dwLocation = ConfigurationManager.AppSettings["SqlDwLocation"]; | ||
var dwuConfigFile = ConfigurationManager.AppSettings["DwuConfigFile"]; | ||
var dwuConfigManager = new DwuConfigManager(dwuConfigFile); | ||
|
||
// Create a DataWarehouseManagementClient | ||
var dwClient = DwClientFactory.Create(resourceId); | ||
// Get database information | ||
var dbInfo = dwClient.GetDatabase(); | ||
dynamic dbInfoObject = JsonConvert.DeserializeObject(dbInfo); | ||
var currentDwu = dbInfoObject.properties.requestedServiceObjectiveName.ToString(); | ||
log.Info($"Current DWU is {currentDwu}"); | ||
|
||
// If current dwu is smaller than default dwu, then scale up to default dwu | ||
if (dwuConfigManager.CompareDwus(currentDwu, dwuConfigManager.DwuConfigs.DefaultDwu) < 0) | ||
{ | ||
log.Info($"Scale up to default {dwuConfigManager.DwuConfigs.DefaultDwu}"); | ||
dwClient.ScaleWarehouse(dwuConfigManager.DwuConfigs.DefaultDwu, dwLocation); | ||
} | ||
else | ||
{ | ||
log.Info($"No need to scale up. Current dwu is same or higher than default dwu {dwuConfigManager.DwuConfigs.DefaultDwu}"); | ||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
log.Info($"ScaleSqlDwByTimer threw exception: {e.Message}"); | ||
} | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDwByTimer/function.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"disabled": false, | ||
"bindings": [ | ||
{ | ||
"name": "myTimer", | ||
"type": "timerTrigger", | ||
"direction": "in", | ||
"schedule": "0 30 16 * * *" | ||
} | ||
], | ||
"scriptFile": "..\\bin\\SqlDwAutoScaler.dll", | ||
"entryPoint": "SqlDwAutoScaler.ScaleSqlDwByTimer.Run" | ||
} |
11 changes: 11 additions & 0 deletions
11
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDwByTimer/readme.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# TimerTrigger - C<span>#</span> | ||
|
||
The `TimerTrigger` makes it incredibly easy to have your functions executed on a schedule. This sample demonstrates a simple use case of calling your function every 5 minutes. | ||
|
||
## How it works | ||
|
||
For a `TimerTrigger` to work, you provide a schedule in the form of a [cron expression](https://en.wikipedia.org/wiki/Cron#CRON_expression)(See the link for full details). A cron expression is a string with 6 separate expressions which represent a given schedule via patterns. The pattern we use to represent every 5 minutes is `0 */5 * * * *`. This, in plain text, means: "When seconds is equal to 0, minutes is divisible by 5, for any hour, day of the month, month, day of the week, or year". | ||
|
||
## Learn more | ||
|
||
<TODO> Documentation |
Empty file.
37 changes: 37 additions & 0 deletions
37
arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/Shared/DwClientFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System; | ||
using System.Configuration; | ||
using Microsoft.IdentityModel.Clients.ActiveDirectory; | ||
using Microsoft.WindowsAzure; | ||
|
||
namespace SqlDwAutoScaler.Shared | ||
{ | ||
public class DwClientFactory | ||
{ | ||
public static string ActiveDirectoryEndpoint { get; set; } = "https://login.windows.net/"; | ||
public static string ResourceManagerEndpoint { get; set; } = "https://management.azure.com/"; | ||
public static string WindowsManagementUri { get; set; } = "https://management.core.windows.net/"; | ||
|
||
// Leave the following Ids and keys unassigned so that they won't be checked in Git. They are assigned in Azure portal. | ||
public static string SubscriptionId { get; set; } = ConfigurationManager.AppSettings["SubscriptionId"]; | ||
public static string TenantId { get; set; } = ConfigurationManager.AppSettings["TenantId"]; | ||
public static string ClientId { get; set; } = ConfigurationManager.AppSettings["ClientId"]; | ||
public static string ClientKey { get; set; } = ConfigurationManager.AppSettings["ClientKey"]; | ||
|
||
public static DwManagementClient Create(string resourceId) | ||
{ | ||
var authenticationContext = new AuthenticationContext(ActiveDirectoryEndpoint + TenantId); | ||
var credential = new ClientCredential(clientId: ClientId, clientSecret: ClientKey); | ||
var result = authenticationContext.AcquireTokenAsync(resource: WindowsManagementUri, clientCredential: credential).Result; | ||
|
||
if (result == null) throw new InvalidOperationException("Failed to obtain the token!"); | ||
|
||
var token = result.AccessToken; | ||
|
||
var aadTokenCredentials = new TokenCloudCredentials(SubscriptionId, token); | ||
|
||
var client = new DwManagementClient(aadTokenCredentials, resourceId); | ||
return client; | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.