Exemple #1
0
        public void DecryptedContextShouldMatchEnvironments()
        {
            var encrypted = EncryptedHostAssignmentContext.Create(_context, _encryptionKey);
            var decrypted = encrypted.Decrypt(_encryptionKey);

            Assert.Equal(_context.Environment, decrypted.Environment);
        }
        public void SetContext_AppliesHostAssignmentContext()
        {
            var context = new HostAssignmentContext
            {
                Environment = new Dictionary <string, string>(),
                SiteName    = "TestSite",
                Secrets     = _secrets
            };
            string json             = JsonConvert.SerializeObject(context);
            string encrypted        = SimpleWebTokenHelper.Encrypt(json, environment: _environment);
            var    encryptedContext = new EncryptedHostAssignmentContext {
                EncryptedContext = encrypted
            };

            var result = _startupContextProvider.SetContext(encryptedContext);

            Assert.Equal(context.SiteName, result.SiteName);
            Assert.Equal(_secrets.Host.Master, result.Secrets.Host.Master);

            var secrets = _startupContextProvider.GetHostSecretsOrNull();

            Assert.Equal(_secrets.Host.Master, secrets.MasterKey);
            Assert.Equal(_secrets.Host.Function, secrets.FunctionKeys);
            Assert.Equal(_secrets.Host.System, secrets.SystemKeys);
        }
Exemple #3
0
        public async Task <IActionResult> Assign([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
        {
            _logger.LogDebug($"Starting container assignment for host : {Request?.Host}. ContextLength is: {encryptedAssignmentContext.EncryptedContext?.Length}");
            var containerKey      = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey);
            var assignmentContext = encryptedAssignmentContext.IsWarmup
                ? null
                : encryptedAssignmentContext.Decrypt(containerKey);

            // before starting the assignment we want to perform as much
            // up front validation on the context as possible
            string error = await _instanceManager.ValidateContext(assignmentContext, encryptedAssignmentContext.IsWarmup);

            if (error != null)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, error));
            }

            // Wait for Sidecar specialization to complete before returning ok.
            // This shouldn't take too long so ok to do this sequentially.
            error = await _instanceManager.SpecializeMSISidecar(assignmentContext, encryptedAssignmentContext.IsWarmup);

            if (error != null)
            {
                return(StatusCode(StatusCodes.Status500InternalServerError, error));
            }

            var result = _instanceManager.StartAssignment(assignmentContext, encryptedAssignmentContext.IsWarmup);

            return(result || encryptedAssignmentContext.IsWarmup
                ? Accepted()
                : StatusCode(StatusCodes.Status409Conflict, "Instance already assigned"));
        }
Exemple #4
0
        public void DecryptedContextShouldMatchByEqual()
        {
            var encrypted = EncryptedHostAssignmentContext.Create(_context, _encryptionKey);
            var decrypted = encrypted.Decrypt(_encryptionKey);

            Assert.True(_context.Equals(decrypted));
        }
Exemple #5
0
        public async Task Assignment_Succeeds_With_Encryption_Key()
        {
            var environment = new TestEnvironment();

            environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1");

            var scriptWebEnvironment = new ScriptWebHostEnvironment(environment);

            var loggerFactory  = new LoggerFactory();
            var loggerProvider = new TestLoggerProvider();

            loggerFactory.AddProvider(loggerProvider);

            var handlerMock = new Mock <HttpMessageHandler>(MockBehavior.Strict);

            handlerMock.Protected().Setup <Task <HttpResponseMessage> >("SendAsync",
                                                                        ItExpr.IsAny <HttpRequestMessage>(),
                                                                        ItExpr.IsAny <CancellationToken>()).ReturnsAsync(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.OK
            });

            var instanceManager = new InstanceManager(_optionsFactory, TestHelpers.CreateHttpClientFactory(handlerMock.Object),
                                                      scriptWebEnvironment, environment, loggerFactory.CreateLogger <InstanceManager>(),
                                                      new TestMetricsLogger(), null, new Mock <IRunFromPackageHandler>().Object,
                                                      new Mock <IPackageDownloadHandler>(MockBehavior.Strict).Object);
            var startupContextProvider = new StartupContextProvider(environment, loggerFactory.CreateLogger <StartupContextProvider>());

            InstanceManager.Reset();

            var podController = new KubernetesPodController(environment, instanceManager, loggerFactory, startupContextProvider);

            const string podEncryptionKey      = "/a/vXvWJ3Hzgx4PFxlDUJJhQm5QVyGiu0NNLFm/ZMMg=";
            var          hostAssignmentContext = new HostAssignmentContext
            {
                Environment = new Dictionary <string, string>()
                {
                    [EnvironmentSettingNames.AzureWebsiteRunFromPackage] = "http://localhost:1234"
                }
            };

            hostAssignmentContext.Secrets         = new FunctionAppSecrets();
            hostAssignmentContext.IsWarmupRequest = false;

            var encryptedHostAssignmentValue = SimpleWebTokenHelper.Encrypt(JsonConvert.SerializeObject(hostAssignmentContext), podEncryptionKey.ToKeyBytes());

            var encryptedHostAssignmentContext = new EncryptedHostAssignmentContext()
            {
                EncryptedContext = encryptedHostAssignmentValue
            };

            environment.SetEnvironmentVariable(EnvironmentSettingNames.PodEncryptionKey, podEncryptionKey);
            environment.SetEnvironmentVariable(EnvironmentSettingNames.KubernetesServiceHost, "http://localhost:80");
            environment.SetEnvironmentVariable(EnvironmentSettingNames.PodNamespace, "k8se-apps");

            var result = await podController.Assign(encryptedHostAssignmentContext);

            Assert.NotNull(startupContextProvider.Context);
            Assert.IsType <AcceptedResult>(result);
        }
Exemple #6
0
        public async Task Assign_MSISpecializationFailure_ReturnsError()
        {
            var environment = new TestEnvironment();

            environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1");

            var scriptWebEnvironment = new ScriptWebHostEnvironment(environment);

            var loggerFactory  = new LoggerFactory();
            var loggerProvider = new TestLoggerProvider();

            loggerFactory.AddProvider(loggerProvider);

            var handlerMock = new Mock <HttpMessageHandler>(MockBehavior.Strict);

            handlerMock.Protected().Setup <Task <HttpResponseMessage> >("SendAsync",
                                                                        ItExpr.IsAny <HttpRequestMessage>(),
                                                                        ItExpr.IsAny <CancellationToken>()).ReturnsAsync(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.BadRequest
            });

            var instanceManager = new InstanceManager(_optionsFactory, new HttpClient(handlerMock.Object),
                                                      scriptWebEnvironment, environment, loggerFactory.CreateLogger <InstanceManager>(),
                                                      new TestMetricsLogger(), null, _runFromPackageHandler.Object);
            var startupContextProvider = new StartupContextProvider(environment, loggerFactory.CreateLogger <StartupContextProvider>());

            InstanceManager.Reset();

            var instanceController = new InstanceController(environment, instanceManager, loggerFactory, startupContextProvider);

            const string containerEncryptionKey = "/a/vXvWJ3Hzgx4PFxlDUJJhQm5QVyGiu0NNLFm/ZMMg=";
            var          hostAssignmentContext  = new HostAssignmentContext
            {
                Environment = new Dictionary <string, string>(),
                MSIContext  = new MSIContext()
            };

            hostAssignmentContext.Environment[EnvironmentSettingNames.MsiEndpoint] = "http://localhost:8081";
            hostAssignmentContext.Environment[EnvironmentSettingNames.MsiSecret]   = "secret";

            var encryptedHostAssignmentValue = SimpleWebTokenHelper.Encrypt(JsonConvert.SerializeObject(hostAssignmentContext), containerEncryptionKey.ToKeyBytes());

            var encryptedHostAssignmentContext = new EncryptedHostAssignmentContext()
            {
                EncryptedContext = encryptedHostAssignmentValue
            };

            environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey, containerEncryptionKey);

            IActionResult result = await instanceController.Assign(encryptedHostAssignmentContext);

            var objectResult = result as ObjectResult;

            Assert.Equal(objectResult.StatusCode, 500);
            Assert.Equal(objectResult.Value, "Specialize MSI sidecar call failed. StatusCode=BadRequest");
        }
Exemple #7
0
        public IActionResult Assign([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
        {
            var containerKey      = _settingsManager.GetSetting(EnvironmentSettingNames.ContainerEncryptionKey);
            var assignmentContext = encryptedAssignmentContext.Decrypt(containerKey);

            return(_instanceManager.StartAssignment(assignmentContext)
                ? Accepted()
                : StatusCode(StatusCodes.Status409Conflict, "Instance already assigned"));
        }
Exemple #8
0
        public async Task Assignment_Does_Not_Set_Secrets_Context_For_Warmup_Request()
        {
            var environment = new TestEnvironment();

            environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1");

            var scriptWebEnvironment = new ScriptWebHostEnvironment(environment);

            var loggerFactory  = new LoggerFactory();
            var loggerProvider = new TestLoggerProvider();

            loggerFactory.AddProvider(loggerProvider);

            var handlerMock = new Mock <HttpMessageHandler>(MockBehavior.Strict);

            handlerMock.Protected().Setup <Task <HttpResponseMessage> >("SendAsync",
                                                                        ItExpr.IsAny <HttpRequestMessage>(),
                                                                        ItExpr.IsAny <CancellationToken>()).ReturnsAsync(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.OK
            });

            var instanceManager = new InstanceManager(_optionsFactory, new HttpClient(handlerMock.Object),
                                                      scriptWebEnvironment, environment, loggerFactory.CreateLogger <InstanceManager>(),
                                                      new TestMetricsLogger(), null, _runFromPackageHandler.Object);
            var startupContextProvider = new StartupContextProvider(environment, loggerFactory.CreateLogger <StartupContextProvider>());

            InstanceManager.Reset();

            var instanceController = new InstanceController(environment, instanceManager, loggerFactory, startupContextProvider);

            const string containerEncryptionKey = "/a/vXvWJ3Hzgx4PFxlDUJJhQm5QVyGiu0NNLFm/ZMMg=";
            var          hostAssignmentContext  = new HostAssignmentContext
            {
                Environment = new Dictionary <string, string>()
                {
                    [EnvironmentSettingNames.AzureWebsiteRunFromPackage] = "http://localhost:1234"
                }
            };

            hostAssignmentContext.Secrets         = new FunctionAppSecrets();
            hostAssignmentContext.IsWarmupRequest = true; // Warmup Request

            var encryptedHostAssignmentValue = SimpleWebTokenHelper.Encrypt(JsonConvert.SerializeObject(hostAssignmentContext), containerEncryptionKey.ToKeyBytes());

            var encryptedHostAssignmentContext = new EncryptedHostAssignmentContext()
            {
                EncryptedContext = encryptedHostAssignmentValue
            };

            environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey, containerEncryptionKey);

            await instanceController.Assign(encryptedHostAssignmentContext);

            Assert.Null(startupContextProvider.Context);
        }
        public async Task <IActionResult> AssignAsync([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
        {
            var containerKey      = System.Environment.GetEnvironmentVariable(SettingsKeys.ContainerEncryptionKey);
            var assignmentContext = encryptedAssignmentContext.Decrypt(containerKey);

            // before starting the assignment we want to perform as much
            // up front validation on the context as possible
            string error = await _instanceManager.ValidateContext(assignmentContext);

            if (error != null)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, error));
            }

            var assignmentResult = _instanceManager.StartAssignment(assignmentContext);

            // modify app settings from environment variables (for all start with "APPSETTING_")
            // setting APPSETTING_FUNCTIONS_EXTENSION_VERSION will both set the appsetting and environment variable
            if (assignmentResult)
            {
                foreach (KeyValuePair <string, string> env in assignmentContext.Environment)
                {
                    string key   = env.Key;
                    string value = env.Value;

                    if (key.StartsWith(_appsettingPrefix))
                    {
                        key = key.Substring(_appsettingPrefix.Length);
                        _settingsManager.SetValue(key, value);
                    }

                    // configure function app specialization properly for Linux Consumption plan
                    Dictionary <string, string> newSettings = FunctionAppSpecializationHelper.HandleLinuxConsumption(key, value);
                    foreach (KeyValuePair <string, string> newSetting in newSettings)
                    {
                        if (System.Environment.GetEnvironmentVariable(newSetting.Key) == null)
                        {
                            System.Environment.SetEnvironmentVariable(newSetting.Key, newSetting.Value);
                        }

                        if (_settingsManager.GetValue(newSetting.Key) == null)
                        {
                            _settingsManager.SetValue(newSetting.Key, newSetting.Value);
                        }
                    }
                }
            }

            return(assignmentResult
               ? Accepted()
               : StatusCode(StatusCodes.Status409Conflict, "Instance already assigned"));
        }
        /// <summary>
        /// Decrypt and deserialize the specified context, and apply values from it to the
        /// startup cache context.
        /// </summary>
        /// <param name="encryptedContext">The encrypted assignment context.</param>
        /// <returns>The decrypted assignment context</returns>
        public virtual HostAssignmentContext SetContext(EncryptedHostAssignmentContext encryptedContext)
        {
            string decryptedContext      = SimpleWebTokenHelper.Decrypt(encryptedContext.EncryptedContext, environment: _environment);
            var    hostAssignmentContext = JsonConvert.DeserializeObject <HostAssignmentContext>(decryptedContext);

            // apply values from the context to our cached context
            Context = new StartupContext
            {
                Secrets = hostAssignmentContext.Secrets
            };

            return(hostAssignmentContext);
        }
Exemple #11
0
        public async Task Assignment_Invokes_InstanceManager_Methods_For_Warmup_Requests_Also(bool isWarmupRequest, bool shouldInvokeMethod)
        {
            var environment = new TestEnvironment();

            environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1");

            var loggerFactory  = new LoggerFactory();
            var loggerProvider = new TestLoggerProvider();

            loggerFactory.AddProvider(loggerProvider);

            var instanceManager        = new Mock <IInstanceManager>();
            var startupContextProvider = new StartupContextProvider(environment, loggerFactory.CreateLogger <StartupContextProvider>());

            InstanceManager.Reset();

            var instanceController = new InstanceController(environment, instanceManager.Object, loggerFactory,
                                                            startupContextProvider);

            const string containerEncryptionKey = "/a/vXvWJ3Hzgx4PFxlDUJJhQm5QVyGiu0NNLFm/ZMMg=";
            var          hostAssignmentContext  = new HostAssignmentContext
            {
                Environment = new Dictionary <string, string>()
            };

            hostAssignmentContext.IsWarmupRequest = isWarmupRequest;

            var encryptedHostAssignmentValue =
                SimpleWebTokenHelper.Encrypt(JsonConvert.SerializeObject(hostAssignmentContext),
                                             containerEncryptionKey.ToKeyBytes());

            var encryptedHostAssignmentContext = new EncryptedHostAssignmentContext()
            {
                EncryptedContext = encryptedHostAssignmentValue
            };

            environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey, containerEncryptionKey);

            await instanceController.Assign(encryptedHostAssignmentContext);

            instanceManager.Verify(i => i.ValidateContext(It.IsAny <HostAssignmentContext>()),
                                   shouldInvokeMethod ? Times.Once() : Times.Never());
            instanceManager.Verify(i => i.SpecializeMSISidecar(It.IsAny <HostAssignmentContext>()),
                                   shouldInvokeMethod ? Times.Once() : Times.Never());
            instanceManager.Verify(i => i.StartAssignment(It.IsAny <HostAssignmentContext>()),
                                   shouldInvokeMethod ? Times.Once() : Times.Never());
        }
Exemple #12
0
        public async Task <IActionResult> Assign([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
        {
            _logger.LogDebug($"Starting container assignment for host : {Request?.Host}");
            var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);

            string error = await _instanceManager.ValidateContext(assignmentContext);

            if (error != null)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, error));
            }

            var succeeded = _instanceManager.StartAssignment(assignmentContext);

            return(succeeded
                ? Accepted()
                : StatusCode(StatusCodes.Status409Conflict, "Instance already assigned"));
        }
        public async Task <IActionResult> Assign([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
        {
            var containerKey      = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey);
            var assignmentContext = encryptedAssignmentContext.Decrypt(containerKey);

            // before starting the assignment we want to perform as much
            // up front validation on the context as possible
            string error = await _instanceManager.ValidateContext(assignmentContext);

            if (error != null)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, error));
            }

            var result = _instanceManager.StartAssignment(assignmentContext);

            return(result
                ? Accepted()
                : StatusCode(StatusCodes.Status409Conflict, "Instance already assigned"));
        }
        private async Task Assign(string encryptionKey)
        {
            // create a zip package
            var contentRoot = Path.Combine(Path.GetTempPath(), @"FunctionsTest");
            var sourcePath  = Path.Combine(Directory.GetCurrentDirectory(), @"TestScripts\Node\HttpTrigger");
            var zipFilePath = Path.Combine(contentRoot, "content.zip");
            await TestHelpers.CreateContentZip(contentRoot, zipFilePath, @"TestScripts\Node\HttpTrigger");

            // upload the blob and get a SAS uri
            var    configuration    = _httpServer.Host.Services.GetService <IConfiguration>();
            string connectionString = configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
            var    sasUri           = await TestHelpers.CreateBlobSas(connectionString, zipFilePath, "azure-functions-test", "appcontents.zip");

            // Now specialize the host by invoking assign
            var    secretManager = _httpServer.Host.Services.GetService <ISecretManagerProvider>().Current;
            var    masterKey     = (await secretManager.GetHostSecretsAsync()).MasterKey;
            string uri           = "admin/instance/assign";
            var    request       = new HttpRequestMessage(HttpMethod.Post, uri);
            var    environment   = new Dictionary <string, string>()
            {
                { EnvironmentSettingNames.AzureWebsiteZipDeployment, sasUri.ToString() },
                { RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName, "~2" },
                { RpcWorkerConstants.FunctionWorkerRuntimeSettingName, "node" }
            };
            var assignmentContext = new HostAssignmentContext
            {
                SiteId      = 1234,
                SiteName    = "TestApp",
                Environment = environment
            };
            var    encryptedAssignmentContext = EncryptedHostAssignmentContext.Create(assignmentContext, encryptionKey);
            string json = JsonConvert.SerializeObject(encryptedAssignmentContext);

            request.Content = new StringContent(json, Encoding.UTF8, "application/json");
            request.Headers.Add(AuthenticationLevelHandler.FunctionsKeyHeaderName, masterKey);
            var response = await _httpClient.SendAsync(request);

            Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
        }
        public async Task <IActionResult> Assign([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext)
        {
            _logger.LogDebug($"Starting container assignment for host : {Request?.Host}. ContextLength is: {encryptedAssignmentContext.EncryptedContext?.Length}");

            bool succeeded = false;

            if (!encryptedAssignmentContext.IsWarmup)
            {
                var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);

                // before starting the assignment we want to perform as much
                // up front validation on the context as possible
                string error = await _instanceManager.ValidateContext(assignmentContext, encryptedAssignmentContext.IsWarmup);

                if (error != null)
                {
                    return(StatusCode(StatusCodes.Status400BadRequest, error));
                }

                // Wait for Sidecar specialization to complete before returning ok.
                // This shouldn't take too long so ok to do this sequentially.
                error = await _instanceManager.SpecializeMSISidecar(assignmentContext, encryptedAssignmentContext.IsWarmup);

                if (error != null)
                {
                    return(StatusCode(StatusCodes.Status500InternalServerError, error));
                }

                succeeded = _instanceManager.StartAssignment(assignmentContext, encryptedAssignmentContext.IsWarmup);
            }
            else
            {
                succeeded = true;
            }

            return(succeeded
                ? Accepted()
                : StatusCode(StatusCodes.Status409Conflict, "Instance already assigned"));
        }
        public void Does_Not_SetContext_AppliesHostAssignmentContext_For_Warmup_Request()
        {
            var context = new HostAssignmentContext
            {
                Environment     = new Dictionary <string, string>(),
                SiteName        = "TestSite",
                Secrets         = _secrets,
                IsWarmupRequest = true
            };
            string json             = JsonConvert.SerializeObject(context);
            string encrypted        = SimpleWebTokenHelper.Encrypt(json, environment: _environment);
            var    encryptedContext = new EncryptedHostAssignmentContext {
                EncryptedContext = encrypted
            };

            var result = _startupContextProvider.SetContext(encryptedContext);

            Assert.Equal(context.SiteName, result.SiteName);
            Assert.Equal(_secrets.Host.Master, result.Secrets.Host.Master);

            var secrets = _startupContextProvider.GetHostSecretsOrNull();

            Assert.Null(secrets);
        }
Exemple #17
0
        public void EncryptedContextShouldExistAfterCreate()
        {
            var result = EncryptedHostAssignmentContext.Create(_context, _encryptionKey);

            Assert.NotEmpty(result.EncryptedContext);
        }
Exemple #18
0
        public async Task StandbyMode_EndToEnd_LinuxContainer()
        {
            byte[] bytes         = TestHelpers.GenerateKeyBytes();
            var    encryptionKey = Convert.ToBase64String(bytes);

            var vars = new Dictionary <string, string>
            {
                { EnvironmentSettingNames.ContainerName, "TestContainer" },
                { EnvironmentSettingNames.ContainerEncryptionKey, encryptionKey },
                { EnvironmentSettingNames.AzureWebsiteContainerReady, null },
                { "AzureWebEncryptionKey", "0F75CA46E7EBDD39E4CA6B074D1F9A5972B849A55F91A248" }
            };

            using (var env = new TestScopedEnvironmentVariable(vars))
            {
                var httpConfig = new HttpConfiguration();

                var testRootPath = Path.Combine(Path.GetTempPath(), "StandbyModeTest_Linux");
                await FileUtility.DeleteDirectoryAsync(testRootPath, true);

                var loggerProvider        = new TestLoggerProvider();
                var loggerProviderFactory = new TestLoggerProviderFactory(loggerProvider);
                var webHostSettings       = new WebHostSettings
                {
                    IsSelfHost  = true,
                    LogPath     = Path.Combine(testRootPath, "Logs"),
                    SecretsPath = Path.Combine(testRootPath, "Secrets"),
                    ScriptPath  = Path.Combine(testRootPath, "WWWRoot")
                };

                var loggerFactory = new LoggerFactory();
                loggerFactory.AddProvider(loggerProvider);

                var webHostBuilder = Program.CreateWebHostBuilder()
                                     .ConfigureServices(c =>
                {
                    c.AddSingleton(webHostSettings)
                    .AddSingleton <ILoggerProviderFactory>(loggerProviderFactory)
                    .AddSingleton <ILoggerFactory>(loggerFactory);
                });

                var httpServer = new TestServer(webHostBuilder);
                var httpClient = httpServer.CreateClient();
                httpClient.BaseAddress = new Uri("https://localhost/");

                TestHelpers.WaitForWebHost(httpClient);

                var traces = loggerProvider.GetAllLogMessages().ToArray();
                Assert.NotNull(traces.Single(p => p.FormattedMessage.StartsWith("Starting Host (HostId=placeholder-host")));
                Assert.NotNull(traces.Single(p => p.FormattedMessage.StartsWith("Host is in standby mode")));

                // issue warmup request and verify
                var request  = new HttpRequestMessage(HttpMethod.Get, "api/warmup");
                var response = await httpClient.SendAsync(request);

                Assert.Equal(HttpStatusCode.OK, response.StatusCode);
                string responseBody = await response.Content.ReadAsStringAsync();

                Assert.Equal("WarmUp complete.", responseBody);

                // issue warmup request with restart and verify
                request  = new HttpRequestMessage(HttpMethod.Get, "api/warmup?restart=1");
                response = await httpClient.SendAsync(request);

                Assert.Equal(HttpStatusCode.OK, response.StatusCode);
                responseBody = await response.Content.ReadAsStringAsync();

                Assert.Equal("WarmUp complete.", responseBody);

                // Now specialize the host by invoking assign
                var    secretManager = httpServer.Host.Services.GetService <ISecretManager>();
                var    masterKey     = (await secretManager.GetHostSecretsAsync()).MasterKey;
                string uri           = "admin/instance/assign";
                request = new HttpRequestMessage(HttpMethod.Post, uri);
                var environment       = new Dictionary <string, string>();
                var assignmentContext = new HostAssignmentContext
                {
                    SiteId      = 1234,
                    SiteName    = "TestSite",
                    Environment = environment
                };
                var    encryptedAssignmentContext = EncryptedHostAssignmentContext.Create(assignmentContext, encryptionKey);
                string json = JsonConvert.SerializeObject(encryptedAssignmentContext);
                request.Content = new StringContent(json, Encoding.UTF8, "application/json");
                request.Headers.Add(AuthenticationLevelHandler.FunctionsKeyHeaderName, masterKey);
                response = await httpClient.SendAsync(request);

                Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);

                // give time for the specialization to happen
                string[] logLines = null;
                await TestHelpers.Await(() =>
                {
                    // wait for the trace indicating that the host has been specialized
                    logLines = loggerProvider.GetAllLogMessages().Where(p => p.FormattedMessage != null).Select(p => p.FormattedMessage).ToArray();
                    return(logLines.Contains("Generating 0 job function(s)"));
                }, userMessageCallback : () => string.Join(Environment.NewLine, loggerProvider.GetAllLogMessages().Select(p => $"[{p.Timestamp.ToString("HH:mm:ss.fff")}] {p.FormattedMessage}")));

                httpServer.Dispose();
                httpClient.Dispose();

                await Task.Delay(2000);

                var hostConfig     = WebHostResolver.CreateScriptHostConfiguration(webHostSettings, true);
                var expectedHostId = hostConfig.HostConfig.HostId;

                // verify the rest of the expected logs
                string text = string.Join(Environment.NewLine, logLines);
                Assert.True(logLines.Count(p => p.Contains("Stopping Host")) >= 1);
                Assert.Equal(1, logLines.Count(p => p.Contains("Creating StandbyMode placeholder function directory")));
                Assert.Equal(1, logLines.Count(p => p.Contains("StandbyMode placeholder function directory created")));
                Assert.Equal(2, logLines.Count(p => p.Contains("Starting Host (HostId=placeholder-host")));
                Assert.Equal(2, logLines.Count(p => p.Contains("Host is in standby mode")));
                Assert.Equal(2, logLines.Count(p => p.Contains("Executed 'Functions.WarmUp' (Succeeded")));
                Assert.Equal(1, logLines.Count(p => p.Contains("Starting host specialization")));
                Assert.Equal(1, logLines.Count(p => p.Contains($"Starting Host (HostId={expectedHostId}")));
                Assert.Contains("Generating 0 job function(s)", logLines);

                WebScriptHostManager.ResetStandbyMode();
            }
        }