Skip to content

Commit

Permalink
ANT86 Patch on Linux Consumption Remote Build (Azure-App-Service#95)
Browse files Browse the repository at this point in the history
* Warm up linux conusmption function app after remote build

* Also take SCM_RUN_FROM_PACKAGE from app setting
  • Loading branch information
Hazhzeng authored and sanchitmehta committed Nov 5, 2019
1 parent 39bd91d commit ebe4426
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 28 deletions.
4 changes: 4 additions & 0 deletions Kudu.Contracts/Deployment/DeploymentInfoBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,9 @@ public bool IsValid()
// won't update until after a process restart. Therefore, we copy the needed
// files into a separate folders and run sync triggers from there.
public string SyncFunctionsTriggersPath { get; set; } = null;

// If doWarmUp is set to true, the after Linux Consumption function app deployment,
// will initiate a GET request to http://appname.azurewebsites.net
public bool DoWarmUp { get; set; }
}
}
2 changes: 1 addition & 1 deletion Kudu.Core/Deployment/DeploymentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ private async Task Build(
// 1. packaging the output folder
// 2. upload the artifact to user's storage account
// 3. reset the container workers after deployment
await LinuxConsumptionDeploymentHelper.SetupLinuxConsumptionFunctionAppDeployment(_environment, _settings, context);
await LinuxConsumptionDeploymentHelper.SetupLinuxConsumptionFunctionAppDeployment(_environment, _settings, context, deploymentInfo.DoWarmUp);
}

await PostDeploymentHelper.SyncFunctionsTriggers(
Expand Down
2 changes: 1 addition & 1 deletion Kudu.Core/Deployment/Generator/OryxBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public override Task Build(DeploymentContext context)
context.RepositoryPath = RepositoryPath;

// Initialize Oryx Args.
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(environment);

if (!args.SkipKuduSync)
{
Expand Down
4 changes: 2 additions & 2 deletions Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ namespace Kudu.Core.Deployment.Oryx
{
public class OryxArgumentsFactory
{
public static IOryxArguments CreateOryxArguments()
public static IOryxArguments CreateOryxArguments(IEnvironment env)
{
if (FunctionAppHelper.LooksLikeFunctionApp())
{
if (FunctionAppHelper.HasScmRunFromPackage())
if (env.IsOnLinuxConsumption)
{
return new LinuxConsumptionFunctionAppOryxArguments();
} else {
Expand Down
38 changes: 34 additions & 4 deletions Kudu.Core/Helpers/LinuxConsumptionDeploymentHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ public class LinuxConsumptionDeploymentHelper
/// Specifically used for Linux Consumption to support Server Side build scenario
/// </summary>
/// <param name="context"></param>
public static async Task SetupLinuxConsumptionFunctionAppDeployment(IEnvironment env, IDeploymentSettingsManager settings, DeploymentContext context)
public static async Task SetupLinuxConsumptionFunctionAppDeployment(
IEnvironment env,
IDeploymentSettingsManager settings,
DeploymentContext context,
bool shouldWarmUp)
{
string sas = System.Environment.GetEnvironmentVariable(Constants.ScmRunFromPackage);
string sas = settings.GetValue(Constants.ScmRunFromPackage) ?? System.Environment.GetEnvironmentVariable(Constants.ScmRunFromPackage);

string builtFolder = context.OutputPath;
string packageFolder = env.DeploymentsPath;
string packageFileName = OryxBuildConstants.FunctionAppBuildSettings.LinuxConsumptionArtifactName;
Expand All @@ -43,6 +48,13 @@ public static async Task SetupLinuxConsumptionFunctionAppDeployment(IEnvironment

// Remove Linux consumption plan functionapp workers for the site
await RemoveLinuxConsumptionFunctionAppWorkers(context);

// Invoke a warmup call to the main function site
if (shouldWarmUp)
{
await Task.Delay(TimeSpan.FromSeconds(5));
await WarmUpFunctionAppSite(context);
}
}

private static async Task LogDependenciesFile(string builtFolder)
Expand Down Expand Up @@ -179,7 +191,7 @@ private static async Task UploadLinuxConsumptionFunctionAppBuiltContent(Deployme
// Check if SCM_RUN_FROM_PACKAGE does exist
if (string.IsNullOrEmpty(sas))
{
context.Logger.Log($"Failed to upload because SCM_RUN_FROM_PACKAGE is not provided.");
context.Logger.Log($"Failed to upload because SCM_RUN_FROM_PACKAGE is not provided or misconfigured by function app setting.");
throw new DeploymentFailedException(new ArgumentException("Failed to upload because SAS is empty."));
}

Expand Down Expand Up @@ -210,7 +222,7 @@ private static async Task RemoveLinuxConsumptionFunctionAppWorkers(DeploymentCon
string webSiteHostName = System.Environment.GetEnvironmentVariable(SettingsKeys.WebsiteHostname);
string sitename = ServerConfiguration.GetApplicationName();

context.Logger.Log($"Reseting all workers for {webSiteHostName}");
context.Logger.Log($"Resetting all workers for {webSiteHostName}");

try
{
Expand All @@ -231,6 +243,24 @@ await OperationManager.AttemptAsync(async () =>
}
}

private static async Task WarmUpFunctionAppSite(DeploymentContext context)
{
string webSiteHostName = System.Environment.GetEnvironmentVariable(SettingsKeys.WebsiteHostname);

context.Logger.Log($"Warming up your function app {webSiteHostName}");

try
{
await OperationManager.AttemptAsync(async () =>
{
await PostDeploymentHelper.WarmUpSiteAsync(webSiteHostName);
}, retries: 3, delayBeforeRetry: 2000);
} catch (HttpRequestException hre)
{
context.Logger.Log($"Warm up function site failed due to {hre.Message}");
}
}

private static string PackageArtifactFromFolder(IEnvironment environment, IDeploymentSettingsManager settings, DeploymentContext context, string srcDirectory, string artifactDirectory, string artifactFilename)
{
context.Logger.Log("Writing the artifacts to a squashfs file");
Expand Down
25 changes: 25 additions & 0 deletions Kudu.Core/Helpers/PostDeploymentHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,31 @@ public static async Task RemoveAllWorkersAsync(string websiteHostname, string si
return;
}

/// <summary>
/// Invoke main site url to warm up function app
/// </summary>
/// <param name="websiteHostname">sitename.azurewebsites.net</param>
/// <returns></returns>
public static async Task WarmUpSiteAsync(string websiteHostname)
{
Uri baseUri = null;
if (!Uri.TryCreate($"http://{websiteHostname}", UriKind.Absolute, out baseUri))
{
throw new ArgumentException($"Malformed URI is used in WarmUpSite");
}
Trace(TraceEventType.Information, "Calling WarmUpSite to warm up your application");

// Initiate GET request
using (var client = HttpClientFactory())
using (var response = await client.GetAsync(baseUri))
{
response.EnsureSuccessStatusCode();
Trace(TraceEventType.Information, "WarmUpSite, statusCode = {0}", response.StatusCode);
}

return;
}

private static void VerifyEnvironments()
{
if (string.IsNullOrEmpty(HttpHost))
Expand Down
5 changes: 0 additions & 5 deletions Kudu.Core/Infrastructure/FunctionAppHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ public static bool LooksLikeFunctionApp()
return !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(Constants.FunctionRunTimeVersion));
}

public static bool HasScmRunFromPackage()
{
return !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(Constants.ScmRunFromPackage));
}

public static bool IsCSharpFunctionFromProjectFile(string projectPath)
{
return VsHelper.IncludesAnyReferencePackage(projectPath, "Microsoft.NET.Sdk.Functions");
Expand Down
6 changes: 5 additions & 1 deletion Kudu.Services/Deployment/PushDeploymentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public PushDeploymentController(
[DisableFormValueModelBinding]
public async Task<IActionResult> ZipPushDeploy(
[FromQuery] bool isAsync = false,
[FromQuery] bool warmUp = false,
[FromQuery] string author = null,
[FromQuery] string authorEmail = null,
[FromQuery] string deployer = DefaultDeployer,
Expand All @@ -79,7 +80,8 @@ public async Task<IActionResult> ZipPushDeploy(
Author = author,
AuthorEmail = authorEmail,
Message = message,
ZipURL = null
ZipURL = null,
DoWarmUp = warmUp
};

if (_settings.RunFromLocalZip())
Expand All @@ -102,6 +104,7 @@ public async Task<IActionResult> ZipPushDeploy(
public async Task<IActionResult> ZipPushDeployViaUrl(
[FromBody] JObject requestJson,
[FromQuery] bool isAsync = false,
[FromQuery] bool warmUp = false,
[FromQuery] string author = null,
[FromQuery] string authorEmail = null,
[FromQuery] string deployer = DefaultDeployer,
Expand All @@ -128,6 +131,7 @@ public async Task<IActionResult> ZipPushDeployViaUrl(
AuthorEmail = authorEmail,
Message = message,
ZipURL = zipUrl,
DoWarmUp = warmUp
};
return await PushDeployAsync(deploymentInfo, isAsync, HttpContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void DefaultTest()

[Theory]
[InlineData("NODE", "8.15", true, false, BuildOptimizationsFlags.CompressModules)]
[InlineData("PYTHON", "3.6", true, false, BuildOptimizationsFlags.None)]
[InlineData("PYTHON", "3.6", true, false, BuildOptimizationsFlags.CompressModules)]
[InlineData("PHP", "7.3", true, false, BuildOptimizationsFlags.None)]
[InlineData("DOTNETCORE", "2.2", true, true, BuildOptimizationsFlags.UseTempDirectory)]
public void ArgumentPropertyTest(string language, string version,
Expand Down Expand Up @@ -82,8 +82,8 @@ public void CommandGenerationTest(string language, string version, string expect
}

[Theory]
[InlineData("1.0", "oryx build RepositoryPath -o OutputPath --platform dotnet --platform-version 1.1 -i BuildTempPath")]
[InlineData("2.0", "oryx build RepositoryPath -o OutputPath --platform dotnet --platform-version 2.1 -i BuildTempPath")]
[InlineData("1.0", "oryx build RepositoryPath -o OutputPath --platform dotnet --platform-version 1.1 -i BuildTempPath --log-file /tmp/test.log")]
[InlineData("2.0", "oryx build RepositoryPath -o OutputPath --platform dotnet --platform-version 2.1 -i BuildTempPath --log-file /tmp/test.log")]
public void DotnetcoreVersionPromotionTest(string version, string expectedCommand)
{
var mockedEnvironment = new Dictionary<string, string>()
Expand Down
30 changes: 20 additions & 10 deletions Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Kudu.Core.Deployment;
using Kudu.Core;
using Kudu.Core.Deployment;
using Kudu.Core.Deployment.Oryx;
using System;
using System.Collections.Generic;
Expand All @@ -12,7 +13,8 @@ public class OryxArgumentsFactoryTests
[Fact]
public void OryxArgumentShouldBeAppService()
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = new TestMockedIEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
Assert.IsType<AppServiceOryxArguments>(args);
}

Expand All @@ -22,7 +24,8 @@ public void OryxArgumentShouldBeFunctionApp()
using (new TestScopedEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "PYTHON"))
using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2"))
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
Assert.IsType<FunctionAppOryxArguments>(args);
}
}
Expand All @@ -34,7 +37,8 @@ public void OryxArgumentShouldBeLinuxConsumption()
using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2"))
using (new TestScopedEnvironmentVariable("SCM_RUN_FROM_PACKAGE", "http://microsoft.com"))
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
Assert.IsType<LinuxConsumptionFunctionAppOryxArguments>(args);
}
}
Expand All @@ -55,7 +59,8 @@ public void OryxArgumentRunOryxBuild(bool expectedRunOryxBuild, params string[]

using (new TestScopedEnvironmentVariable(env))
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
Assert.Equal(expectedRunOryxBuild, args.RunOryxBuild);
}
}
Expand All @@ -76,7 +81,8 @@ public void OryxArgumentSkipKuduSync(bool expectedSkipKuduSync, params string[]

using (new TestScopedEnvironmentVariable(env))
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
Assert.Equal(expectedSkipKuduSync, args.SkipKuduSync);
}
}
Expand All @@ -88,7 +94,8 @@ public void BuildCommandForAppService()
{
OutputPath = "outputpath"
};
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
string command = args.GenerateOryxBuildCommand(deploymentContext);
Assert.Equal(@"oryx build outputpath -o outputpath", command);
}
Expand All @@ -104,7 +111,8 @@ public void BuildCommandForFunctionApp()

using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2"))
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
string command = args.GenerateOryxBuildCommand(deploymentContext);
Assert.Equal(@"oryx build outputpath -o outputpath -i buildtemppath", command);
}
Expand All @@ -121,7 +129,8 @@ public void BuildCommandForLinuxConsumptionFunctionApp()
using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2"))
using (new TestScopedEnvironmentVariable("SCM_RUN_FROM_PACKAGE", "http://microsoft.com"))
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
string command = args.GenerateOryxBuildCommand(deploymentContext);
Assert.Equal(@"oryx build repositorypath -o repositorypath", command);
}
Expand All @@ -139,7 +148,8 @@ public void BuildCommandForPythonFunctionApp()
using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2"))
using (new TestScopedEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "python"))
{
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments();
IEnvironment ienv = TestMockedEnvironment.GetMockedEnvironment();
IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(ienv);
string command = args.GenerateOryxBuildCommand(deploymentContext);
Assert.Equal(@"oryx build outputpath -o outputpath --platform python --platform-version 3.6 -i buildtemppath -p packagedir=.python_packages\lib\python3.6\site-packages", command);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void DefaultTest()
OutputPath = "OutputPath"
};
string command = args.GenerateOryxBuildCommand(mockedContext);
Assert.Equal("oryx build RepositoryPath -o RepositoryPath", command);
Assert.Equal("oryx build RepositoryPath -o OutputPath", command);
}

[Theory]
Expand Down
Loading

0 comments on commit ebe4426

Please sign in to comment.