Skip to content

Commit

Permalink
changed files
Browse files Browse the repository at this point in the history
  • Loading branch information
hirokib committed Oct 18, 2017
1 parent bde709f commit 4a04856
Show file tree
Hide file tree
Showing 23 changed files with 1,389 additions and 0 deletions.
22 changes: 22 additions & 0 deletions arm-templates/sqlDwAutoScaler/SqlDwAutoScaler.sln
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 added arm-templates/sqlDwAutoScaler/SqlDwAutoScaler.zip
Binary file not shown.
23 changes: 23 additions & 0 deletions arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/.gitignore
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 arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDw/ScaleSqlDw.cs
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;
}
}
}
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 arm-templates/sqlDwAutoScaler/SqlDwAutoScaler/ScaleSqlDw/readme.md
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "Azure"
}
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}");
}
}
}
}
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"
}
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.
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;
}

}
}
Loading

0 comments on commit 4a04856

Please sign in to comment.