public void LockFileRepositoryClearLockTest() { // Mock var path = @"x:\temp"; var lockFileName = Path.Combine(path, "deployment.lock"); var fileSystem = new Mock <IFileSystem>(); var file = new Mock <FileBase>(); var directory = new Mock <DirectoryBase>(); var repository = new Mock <IRepository>(); var repositoryFactory = new Mock <IRepositoryFactory>(); var lockFile = new DeploymentLockFile(lockFileName, Mock.Of <ITraceFactory>()); // Setup fileSystem.SetupGet(f => f.Directory) .Returns(directory.Object); directory.Setup(d => d.Exists(path)) .Returns(true); fileSystem.SetupGet(f => f.File) .Returns(file.Object); file.Setup(f => f.Open(lockFileName, FileMode.Create, FileAccess.Write, FileShare.Read)) .Returns(Mock.Of <Stream>()); repositoryFactory.Setup(f => f.GetRepository()) .Returns(repository.Object); FileSystemHelpers.Instance = fileSystem.Object; // Test lockFile.RepositoryFactory = repositoryFactory.Object; var locked = lockFile.Lock(); // Assert Assert.True(locked, "lock should be successfule!"); repository.Verify(r => r.ClearLock(), Times.Once); }
// <summary> // This method initializes status,ssh,hooks & deployment locks used by Kudu to ensure // synchronized operations. This method creates a dictionary of locks which is injected // into various controllers to resolve the locks they need. // <list type="bullet"> // <listheader> // <term>Locks used by Kudu:</term> // </listheader> // <item> // <term>Status Lock</term> // <description>Used by DeploymentStatusManager</description> // </item> // <item> // <term>SSH Lock</term> // <description>Used by SSHKeyController</description> // </item> // <item> // <term>Hooks Lock</term> // <description>Used by WebHooksManager</description> // </item> // <item> // <term>Deployment Lock</term> // <description> // Used by DeploymentController, DeploymentManager, SettingsController, // FetchDeploymentManager, LiveScmController, ReceivePackHandlerMiddleware // </description> // </item> // </list> // </summary> // <remarks> // Uses File watcher. // This originally used Ninject's "WhenInjectedInto" in .Net project for specific instances. IServiceCollection // doesn't support this concept, or anything similar like named instances. There are a few possibilities, // but the hack solution for now is just injecting a dictionary of locks and letting each dependent resolve // the one it needs. // </remarks> private static void SetupLocks(ITraceFactory traceFactory, IEnvironment environment) { var lockPath = Path.Combine(environment.SiteRootPath, Constants.LockPath); var deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); var statusLockPath = Path.Combine(lockPath, Constants.StatusLockFile); var sshKeyLockPath = Path.Combine(lockPath, Constants.SSHKeyLockFile); var hooksLockPath = Path.Combine(lockPath, Constants.HooksLockFile); _deploymentLock = DeploymentLockFile.GetInstance(deploymentLockPath, traceFactory); _deploymentLock.InitializeAsyncLocks(); var statusLock = new LockFile(statusLockPath, traceFactory); statusLock.InitializeAsyncLocks(); var sshKeyLock = new LockFile(sshKeyLockPath, traceFactory); sshKeyLock.InitializeAsyncLocks(); var hooksLock = new LockFile(hooksLockPath, traceFactory); hooksLock.InitializeAsyncLocks(); _namedLocks = new Dictionary <string, IOperationLock> { { "status", statusLock }, { "ssh", sshKeyLock }, { "hooks", hooksLock }, { "deployment", _deploymentLock } }; }
// key goal is to create background tracer that is independent of request. public static void PerformBackgroundDeployment(DeploymentInfo deployInfo, IEnvironment environment, IDeploymentSettingsManager settings, TraceLevel traceLevel, Uri uri) { var tracer = traceLevel <= TraceLevel.Off ? NullTracer.Instance : new XmlTracer(environment.TracePath, traceLevel); var traceFactory = new TracerFactory(() => tracer); var backgroundTrace = tracer.Step(XmlTracer.BackgroundTrace, new Dictionary <string, string> { { "url", uri.AbsolutePath }, { "method", "POST" } }); Task.Run(() => { try { // lock related string lockPath = Path.Combine(environment.SiteRootPath, Constants.LockPath); string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); string statusLockPath = Path.Combine(lockPath, Constants.StatusLockFile); string hooksLockPath = Path.Combine(lockPath, Constants.HooksLockFile); var statusLock = new LockFile(statusLockPath, traceFactory); var hooksLock = new LockFile(hooksLockPath, traceFactory); var deploymentLock = new DeploymentLockFile(deploymentLockPath, traceFactory); var analytics = new Analytics(settings, new ServerConfiguration(), traceFactory); var deploymentStatusManager = new DeploymentStatusManager(environment, analytics, statusLock); var repositoryFactory = new RepositoryFactory(environment, settings, traceFactory); var siteBuilderFactory = new SiteBuilderFactory(new BuildPropertyProvider(), environment); var webHooksManager = new WebHooksManager(tracer, environment, hooksLock); var deploymentManager = new DeploymentManager(siteBuilderFactory, environment, traceFactory, analytics, settings, deploymentStatusManager, deploymentLock, NullLogger.Instance, webHooksManager); var fetchHandler = new FetchHandler(tracer, deploymentManager, settings, deploymentStatusManager, deploymentLock, environment, null, repositoryFactory, null); // Perform deployment var acquired = deploymentLock.TryLockOperation(() => { fetchHandler.PerformDeployment(deployInfo).Wait(); }, TimeSpan.Zero); if (!acquired) { using (tracer.Step("Update pending deployment marker file")) { // REVIEW: This makes the assumption that the repository url is the same. // If it isn't the result would be buggy either way. FileSystemHelpers.SetLastWriteTimeUtc(fetchHandler._markerFilePath, DateTime.UtcNow); } } } catch (Exception ex) { tracer.TraceError(ex); } finally { backgroundTrace.Dispose(); } }); }
/// <summary> /// Stops the application. /// </summary> public static void Stop() { if (Shutdown != null) { Shutdown(); } if (_deploymentLock != null) { _deploymentLock.TerminateAsyncLocks(); _deploymentLock = null; } _bootstrapper.ShutDown(); }
public void DeploymentLockSentinelTest() { // Mock System.Environment.SetEnvironmentVariable("WEBSITE_INSTANCE_ID", "Instance1"); var path = @"x:\temp1"; var pathSentinel = "C:\\local\\ShutdownSentinel\\Sentinel.txt"; var lockFileName = Path.Combine(path, "deployment1.lock"); var fileSystem = new Mock <IFileSystem>(); var file = new Mock <FileBase>(); var fileInfoBase = new Mock <IFileInfoFactory>(); var directory = new Mock <DirectoryBase>(); var repository = new Mock <IRepository>(); var repositoryFactory = new Mock <IRepositoryFactory>(); var lockFile = new DeploymentLockFile(lockFileName, Mock.Of <ITraceFactory>(f => f.GetTracer() == Mock.Of <ITracer>())); // Setup fileSystem.SetupGet(f => f.Directory) .Returns(directory.Object); directory.Setup(d => d.Exists(path)) .Returns(true); fileSystem.SetupGet(f => f.File) .Returns(file.Object); file.Setup(f => f.Open(lockFileName, FileMode.Create, FileAccess.Write, FileShare.Read)) .Returns(Mock.Of <Stream>()); file.Setup(f => f.Create(pathSentinel)).Returns(Mock.Of <Stream>()); fileInfoBase.Setup(f => f.FromFileName(pathSentinel)).Returns(Mock.Of <FileInfoBase>()); fileSystem.SetupGet(f => f.FileInfo).Returns(fileInfoBase.Object); repositoryFactory.Setup(f => f.GetRepository()) .Returns(repository.Object); FileSystemHelpers.Instance = fileSystem.Object; lockFile.RepositoryFactory = repositoryFactory.Object; // Test var locked = lockFile.Lock("operationName"); file.Setup(f => f.Exists(pathSentinel)).Returns(true); Assert.True(locked, "lock should be successful!"); Assert.True(FileSystemHelpers.FileExists(pathSentinel), "Sentinel file should be present"); repository.Verify(r => r.ClearLock(), Times.Once); // Reset IsAzureEnvironment env variable System.Environment.SetEnvironmentVariable("WEBSITE_INSTANCE_ID", null); }
private static void RegisterServices(IKernel kernel) { var serverConfiguration = new ServerConfiguration(); // Make sure %HOME% is correctly set EnsureHomeEnvironmentVariable(); EnsureSiteBitnessEnvironmentVariable(); IEnvironment environment = GetEnvironment(); EnsureDotNetCoreEnvironmentVariable(environment); // Add various folders that never change to the process path. All child processes will inherit PrependFoldersToPath(environment); // Per request environment kernel.Bind <IEnvironment>().ToMethod(context => GetEnvironment(context.Kernel.Get <IDeploymentSettingsManager>(), HttpContext.Current)) .InRequestScope(); // General kernel.Bind <IServerConfiguration>().ToConstant(serverConfiguration); kernel.Bind <IBuildPropertyProvider>().ToConstant(new BuildPropertyProvider()); System.Func <ITracer> createTracerThunk = () => GetTracer(kernel); // First try to use the current request profiler if any, otherwise create a new one var traceFactory = new TracerFactory(() => TraceServices.CurrentRequestTracer ?? createTracerThunk()); kernel.Bind <ITracer>().ToMethod(context => TraceServices.CurrentRequestTracer ?? NullTracer.Instance); kernel.Bind <ITraceFactory>().ToConstant(traceFactory); TraceServices.SetTraceFactory(createTracerThunk); // Setup the deployment lock string lockPath = Path.Combine(environment.SiteRootPath, Constants.LockPath); string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); string statusLockPath = Path.Combine(lockPath, Constants.StatusLockFile); string sshKeyLockPath = Path.Combine(lockPath, Constants.SSHKeyLockFile); string hooksLockPath = Path.Combine(lockPath, Constants.HooksLockFile); _deploymentLock = new DeploymentLockFile(deploymentLockPath, kernel.Get <ITraceFactory>()); _deploymentLock.InitializeAsyncLocks(); var statusLock = new LockFile(statusLockPath, kernel.Get <ITraceFactory>()); var sshKeyLock = new LockFile(sshKeyLockPath, kernel.Get <ITraceFactory>()); var hooksLock = new LockFile(hooksLockPath, kernel.Get <ITraceFactory>()); kernel.Bind <IOperationLock>().ToConstant(sshKeyLock).WhenInjectedInto <SSHKeyController>(); kernel.Bind <IOperationLock>().ToConstant(statusLock).WhenInjectedInto <DeploymentStatusManager>(); kernel.Bind <IOperationLock>().ToConstant(hooksLock).WhenInjectedInto <WebHooksManager>(); kernel.Bind <IOperationLock>().ToConstant(_deploymentLock); var shutdownDetector = new ShutdownDetector(); shutdownDetector.Initialize(); IDeploymentSettingsManager noContextDeploymentsSettingsManager = new DeploymentSettingsManager(new XmlSettings.Settings(GetSettingsPath(environment))); TraceServices.TraceLevel = noContextDeploymentsSettingsManager.GetTraceLevel(); var noContextTraceFactory = new TracerFactory(() => GetTracerWithoutContext(environment, noContextDeploymentsSettingsManager)); var etwTraceFactory = new TracerFactory(() => new ETWTracer(string.Empty, string.Empty)); kernel.Bind <IAnalytics>().ToMethod(context => new Analytics(context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <IServerConfiguration>(), noContextTraceFactory)); // Trace unhandled (crash) exceptions. AppDomain.CurrentDomain.UnhandledException += (sender, args) => { var ex = args.ExceptionObject as Exception; if (ex != null) { kernel.Get <IAnalytics>().UnexpectedException(ex); } }; // Trace shutdown event // Cannot use shutdownDetector.Token.Register because of race condition // with NinjectServices.Stop via WebActivator.ApplicationShutdownMethodAttribute Shutdown += () => TraceShutdown(environment, noContextDeploymentsSettingsManager); // LogStream service // The hooks and log stream start endpoint are low traffic end-points. Re-using it to avoid creating another lock var logStreamManagerLock = hooksLock; kernel.Bind <LogStreamManager>().ToMethod(context => new LogStreamManager(Path.Combine(environment.RootPath, Constants.LogFilesPath), context.Kernel.Get <IEnvironment>(), context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITracer>(), shutdownDetector, logStreamManagerLock)); kernel.Bind <InfoRefsController>().ToMethod(context => new InfoRefsController(t => context.Kernel.Get(t))) .InRequestScope(); kernel.Bind <CustomGitRepositoryHandler>().ToMethod(context => new CustomGitRepositoryHandler(t => context.Kernel.Get(t))) .InRequestScope(); // Deployment Service kernel.Bind <ISettings>().ToMethod(context => new XmlSettings.Settings(GetSettingsPath(environment))) .InRequestScope(); kernel.Bind <IDeploymentSettingsManager>().To <DeploymentSettingsManager>() .InRequestScope(); kernel.Bind <IDeploymentStatusManager>().To <DeploymentStatusManager>() .InRequestScope(); kernel.Bind <ISiteBuilderFactory>().To <SiteBuilderFactory>() .InRequestScope(); kernel.Bind <IWebHooksManager>().To <WebHooksManager>() .InRequestScope(); ITriggeredJobsManager triggeredJobsManager = new TriggeredJobsManager( etwTraceFactory, kernel.Get <IEnvironment>(), kernel.Get <IDeploymentSettingsManager>(), kernel.Get <IAnalytics>(), kernel.Get <IWebHooksManager>()); kernel.Bind <ITriggeredJobsManager>().ToConstant(triggeredJobsManager) .InTransientScope(); TriggeredJobsScheduler triggeredJobsScheduler = new TriggeredJobsScheduler( triggeredJobsManager, etwTraceFactory, environment, kernel.Get <IDeploymentSettingsManager>(), kernel.Get <IAnalytics>()); kernel.Bind <TriggeredJobsScheduler>().ToConstant(triggeredJobsScheduler) .InTransientScope(); IContinuousJobsManager continuousJobManager = new ContinuousJobsManager( etwTraceFactory, kernel.Get <IEnvironment>(), kernel.Get <IDeploymentSettingsManager>(), kernel.Get <IAnalytics>()); OperationManager.SafeExecute(triggeredJobsManager.CleanupDeletedJobs); OperationManager.SafeExecute(continuousJobManager.CleanupDeletedJobs); kernel.Bind <IContinuousJobsManager>().ToConstant(continuousJobManager) .InTransientScope(); kernel.Bind <ILogger>().ToMethod(context => GetLogger(environment, context.Kernel)) .InRequestScope(); kernel.Bind <IDeploymentManager>().To <DeploymentManager>() .InRequestScope(); kernel.Bind <IFetchDeploymentManager>().To <FetchDeploymentManager>() .InRequestScope(); kernel.Bind <ISSHKeyManager>().To <SSHKeyManager>() .InRequestScope(); kernel.Bind <IRepositoryFactory>().ToMethod(context => _deploymentLock.RepositoryFactory = new RepositoryFactory(context.Kernel.Get <IEnvironment>(), context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITraceFactory>())) .InRequestScope(); kernel.Bind <IApplicationLogsReader>().To <ApplicationLogsReader>() .InSingletonScope(); // Git server kernel.Bind <IDeploymentEnvironment>().To <DeploymentEnvrionment>(); kernel.Bind <IGitServer>().ToMethod(context => new GitExeServer(context.Kernel.Get <IEnvironment>(), _deploymentLock, GetRequestTraceFile(context.Kernel), context.Kernel.Get <IRepositoryFactory>(), context.Kernel.Get <IDeploymentEnvironment>(), context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITraceFactory>())) .InRequestScope(); // Git Servicehook parsers kernel.Bind <IServiceHookHandler>().To <GenericHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <GitHubHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <BitbucketHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <BitbucketHandlerV2>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <DropboxHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <CodePlexHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <CodebaseHqHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <GitlabHqHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <GitHubCompatHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <KilnHgHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <VSOHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <OneDriveHandler>().InRequestScope(); // SiteExtensions kernel.Bind <ISiteExtensionManager>().To <SiteExtensionManager>().InRequestScope(); // Functions kernel.Bind <IFunctionManager>().To <FunctionManager>().InRequestScope(); // Command executor kernel.Bind <ICommandExecutor>().To <CommandExecutor>().InRequestScope(); MigrateSite(environment, noContextDeploymentsSettingsManager); RemoveOldTracePath(environment); RemoveTempFileFromUserDrive(environment); // Temporary fix for https://github.com/npm/npm/issues/5905 EnsureNpmGlobalDirectory(); EnsureUserProfileDirectory(); // Skip SSL Certificate Validate if (Kudu.Core.Environment.SkipSslValidation) { ServicePointManager.ServerCertificateValidationCallback = delegate { return(true); }; } // Make sure webpages:Enabled is true. Even though we set it in web.config, it could be overwritten by // an Azure AppSetting that's supposed to be for the site only but incidently affects Kudu as well. ConfigurationManager.AppSettings["webpages:Enabled"] = "true"; // Kudu does not rely owin:appStartup. This is to avoid Azure AppSetting if set. if (ConfigurationManager.AppSettings["owin:appStartup"] != null) { // Set the appSetting to null since we cannot use AppSettings.Remove(key) (ReadOnly exception!) ConfigurationManager.AppSettings["owin:appStartup"] = null; } RegisterRoutes(kernel, RouteTable.Routes); // Register the default hubs route: ~/signalr GlobalHost.DependencyResolver = new SignalRNinjectDependencyResolver(kernel); GlobalConfiguration.Configuration.Filters.Add( new TraceDeprecatedActionAttribute( kernel.Get <IAnalytics>(), kernel.Get <ITraceFactory>())); GlobalConfiguration.Configuration.Filters.Add(new EnsureRequestIdHandlerAttribute()); }
// key goal is to create background tracer that is independent of request. public static async Task <bool> PerformBackgroundDeployment( DeploymentInfoBase deployInfo, IEnvironment environment, IDeploymentSettingsManager settings, TraceLevel traceLevel, Uri uri, bool waitForTempDeploymentCreation) { var tracer = traceLevel <= TraceLevel.Off ? NullTracer.Instance : new CascadeTracer(new XmlTracer(environment.TracePath, traceLevel), new ETWTracer(environment.RequestId, "POST")); var traceFactory = new TracerFactory(() => tracer); var backgroundTrace = tracer.Step(XmlTracer.BackgroundTrace, new Dictionary <string, string> { { "url", uri.AbsolutePath }, { "method", "POST" } }); // For waiting on creation of temp deployment var tempDeploymentCreatedTcs = new TaskCompletionSource <object>(); // For determining whether or not we failed to create the deployment due to lock contention. // Needed for deployments where deferred deployment is not allowed. Will be set to false if // lock contention occurs and AllowDeferredDeployment is false, otherwise true. var deploymentWillOccurTcs = new TaskCompletionSource <bool>(); // This task will be let out of scope intentionally var deploymentTask = Task.Run(() => { try { // lock related string lockPath = Path.Combine(environment.SiteRootPath, Constants.LockPath); string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); string statusLockPath = Path.Combine(lockPath, Constants.StatusLockFile); string hooksLockPath = Path.Combine(lockPath, Constants.HooksLockFile); var statusLock = new LockFile(statusLockPath, traceFactory, traceLock: false); var hooksLock = new LockFile(hooksLockPath, traceFactory); var deploymentLock = new DeploymentLockFile(deploymentLockPath, traceFactory); var analytics = new Analytics(settings, new ServerConfiguration(), traceFactory); var deploymentStatusManager = new DeploymentStatusManager(environment, analytics, statusLock); var siteBuilderFactory = new SiteBuilderFactory(new BuildPropertyProvider(), environment); var webHooksManager = new WebHooksManager(tracer, environment, hooksLock); var deploymentManager = new DeploymentManager(siteBuilderFactory, environment, traceFactory, analytics, settings, deploymentStatusManager, deploymentLock, NullLogger.Instance, webHooksManager); var fetchDeploymentManager = new FetchDeploymentManager(settings, environment, tracer, deploymentLock, deploymentManager, deploymentStatusManager); IDisposable tempDeployment = null; try { // Perform deployment deploymentLock.LockOperation(() => { deploymentWillOccurTcs.TrySetResult(true); ChangeSet tempChangeSet = null; if (waitForTempDeploymentCreation) { // create temporary deployment before the actual deployment item started // this allows portal ui to readily display on-going deployment (not having to wait for fetch to complete). // in addition, it captures any failure that may occur before the actual deployment item started tempDeployment = deploymentManager.CreateTemporaryDeployment( Resources.ReceivingChanges, out tempChangeSet, deployInfo.TargetChangeset, deployInfo.Deployer); tempDeploymentCreatedTcs.TrySetResult(null); } fetchDeploymentManager.PerformDeployment(deployInfo, tempDeployment, tempChangeSet).Wait(); }, "Performing continuous deployment", TimeSpan.Zero); } catch (LockOperationException) { if (tempDeployment != null) { OperationManager.SafeExecute(() => tempDeployment.Dispose()); } if (deployInfo.AllowDeferredDeployment) { deploymentWillOccurTcs.TrySetResult(true); using (tracer.Step("Update pending deployment marker file")) { // REVIEW: This makes the assumption that the repository url is the same. // If it isn't the result would be buggy either way. FileSystemHelpers.SetLastWriteTimeUtc(fetchDeploymentManager._markerFilePath, DateTime.UtcNow); } } } } catch (Exception ex) { tracer.TraceError(ex); } finally { // Will no-op if already set deploymentWillOccurTcs.TrySetResult(false); backgroundTrace.Dispose(); } }); #pragma warning disable 4014 // Run on BG task (Task.Run) to avoid ASP.NET Request thread terminated with request completion and // it doesn't get chance to clean up the pending marker. Task.Run(() => PostDeploymentHelper.TrackPendingOperation(deploymentTask, TimeSpan.Zero)); #pragma warning restore 4014 // When the frontend/ARM calls /deploy with isAsync=true, it starts polling // the deployment status immediately, so it's important that the temp deployment // is created before we return. if (waitForTempDeploymentCreation) { // deploymentTask may return without creating the temp deployment (lock contention, // other exception), in which case just continue. await Task.WhenAny(tempDeploymentCreatedTcs.Task, deploymentTask); } // If deferred deployment is not permitted, we need to know whether or not the deployment was // successfully requested. Otherwise, to preserve existing behavior, we assume it was. if (!deployInfo.AllowDeferredDeployment) { return(await deploymentWillOccurTcs.Task); } else { return(true); } }
private static void RegisterServices(IKernel kernel) { var serverConfiguration = new ServerConfiguration(); // Make sure %HOME% is correctly set EnsureHomeEnvironmentVariable(); IEnvironment environment = GetEnvironment(); // Per request environment kernel.Bind <IEnvironment>().ToMethod(context => GetEnvironment(context.Kernel.Get <IDeploymentSettingsManager>())) .InRequestScope(); // General kernel.Bind <HttpContextBase>().ToMethod(context => new HttpContextWrapper(HttpContext.Current)) .InRequestScope(); kernel.Bind <IServerConfiguration>().ToConstant(serverConfiguration); kernel.Bind <IBuildPropertyProvider>().ToConstant(new BuildPropertyProvider()); System.Func <ITracer> createTracerThunk = () => GetTracer(environment, kernel); System.Func <ILogger> createLoggerThunk = () => GetLogger(environment, kernel); // First try to use the current request profiler if any, otherwise create a new one var traceFactory = new TracerFactory(() => TraceServices.CurrentRequestTracer ?? createTracerThunk()); kernel.Bind <ITracer>().ToMethod(context => TraceServices.CurrentRequestTracer ?? NullTracer.Instance); kernel.Bind <ITraceFactory>().ToConstant(traceFactory); TraceServices.SetTraceFactory(createTracerThunk, createLoggerThunk); // Setup the deployment lock string lockPath = Path.Combine(environment.SiteRootPath, Constants.LockPath); string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); string statusLockPath = Path.Combine(lockPath, Constants.StatusLockFile); string sshKeyLockPath = Path.Combine(lockPath, Constants.SSHKeyLockFile); string hooksLockPath = Path.Combine(lockPath, Constants.HooksLockFile); _deploymentLock = new DeploymentLockFile(deploymentLockPath, kernel.Get <ITraceFactory>()); _deploymentLock.InitializeAsyncLocks(); var statusLock = new LockFile(statusLockPath, kernel.Get <ITraceFactory>()); var sshKeyLock = new LockFile(sshKeyLockPath, kernel.Get <ITraceFactory>()); var hooksLock = new LockFile(hooksLockPath, kernel.Get <ITraceFactory>()); kernel.Bind <IOperationLock>().ToConstant(sshKeyLock).WhenInjectedInto <SSHKeyController>(); kernel.Bind <IOperationLock>().ToConstant(statusLock).WhenInjectedInto <DeploymentStatusManager>(); kernel.Bind <IOperationLock>().ToConstant(hooksLock).WhenInjectedInto <WebHooksManager>(); kernel.Bind <IOperationLock>().ToConstant(_deploymentLock); kernel.Bind <IAnalytics>().ToMethod(context => new Analytics(context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITracer>(), environment.AnalyticsPath)); var shutdownDetector = new ShutdownDetector(); shutdownDetector.Initialize(); IDeploymentSettingsManager noContextDeploymentsSettingsManager = new DeploymentSettingsManager(new XmlSettings.Settings(GetSettingsPath(environment))); // Trace shutdown event // Cannot use shutdownDetector.Token.Register because of race condition // with NinjectServices.Stop via WebActivator.ApplicationShutdownMethodAttribute Shutdown += () => TraceShutdown(environment, noContextDeploymentsSettingsManager); // LogStream service // The hooks and log stream start endpoint are low traffic end-points. Re-using it to avoid creating another lock var logStreamManagerLock = hooksLock; kernel.Bind <LogStreamManager>().ToMethod(context => new LogStreamManager(Path.Combine(environment.RootPath, Constants.LogFilesPath), context.Kernel.Get <IEnvironment>(), context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITracer>(), shutdownDetector, logStreamManagerLock)); kernel.Bind <InfoRefsController>().ToMethod(context => new InfoRefsController(t => context.Kernel.Get(t))) .InRequestScope(); kernel.Bind <CustomGitRepositoryHandler>().ToMethod(context => new CustomGitRepositoryHandler(t => context.Kernel.Get(t))) .InRequestScope(); // Deployment Service kernel.Bind <ISettings>().ToMethod(context => new XmlSettings.Settings(GetSettingsPath(environment))) .InRequestScope(); kernel.Bind <IDeploymentSettingsManager>().To <DeploymentSettingsManager>() .InRequestScope(); kernel.Bind <IDeploymentStatusManager>().To <DeploymentStatusManager>() .InRequestScope(); kernel.Bind <ISiteBuilderFactory>().To <SiteBuilderFactory>() .InRequestScope(); kernel.Bind <IWebHooksManager>().To <WebHooksManager>() .InRequestScope(); var noContextTraceFactory = new TracerFactory(() => GetTracerWithoutContext(environment, noContextDeploymentsSettingsManager)); ITriggeredJobsManager triggeredJobsManager = new TriggeredJobsManager( noContextTraceFactory, kernel.Get <IEnvironment>(), kernel.Get <IDeploymentSettingsManager>(), kernel.Get <IAnalytics>(), kernel.Get <IWebHooksManager>()); kernel.Bind <ITriggeredJobsManager>().ToConstant(triggeredJobsManager) .InTransientScope(); IContinuousJobsManager continuousJobManager = new ContinuousJobsManager( noContextTraceFactory, kernel.Get <IEnvironment>(), kernel.Get <IDeploymentSettingsManager>(), kernel.Get <IAnalytics>()); kernel.Bind <IContinuousJobsManager>().ToConstant(continuousJobManager) .InTransientScope(); kernel.Bind <ILogger>().ToMethod(context => GetLogger(environment, context.Kernel)) .InRequestScope(); kernel.Bind <IRepository>().ToMethod(context => new GitExeRepository(context.Kernel.Get <IEnvironment>(), context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITraceFactory>())) .InRequestScope(); kernel.Bind <IDeploymentManager>().To <DeploymentManager>() .InRequestScope(); kernel.Bind <ISSHKeyManager>().To <SSHKeyManager>() .InRequestScope(); kernel.Bind <IRepositoryFactory>().ToMethod(context => _deploymentLock.RepositoryFactory = new RepositoryFactory(context.Kernel.Get <IEnvironment>(), context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITraceFactory>(), context.Kernel.Get <HttpContextBase>())) .InRequestScope(); kernel.Bind <IApplicationLogsReader>().To <ApplicationLogsReader>() .InSingletonScope(); // Git server kernel.Bind <IDeploymentEnvironment>().To <DeploymentEnvrionment>(); kernel.Bind <IGitServer>().ToMethod(context => new GitExeServer(context.Kernel.Get <IEnvironment>(), _deploymentLock, GetRequestTraceFile(context.Kernel), context.Kernel.Get <IRepositoryFactory>(), context.Kernel.Get <IDeploymentEnvironment>(), context.Kernel.Get <IDeploymentSettingsManager>(), context.Kernel.Get <ITraceFactory>())) .InRequestScope(); // Git Servicehook parsers kernel.Bind <IServiceHookHandler>().To <GenericHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <GitHubHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <BitbucketHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <DropboxHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <CodePlexHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <CodebaseHqHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <GitlabHqHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <GitHubCompatHandler>().InRequestScope(); kernel.Bind <IServiceHookHandler>().To <KilnHgHandler>().InRequestScope(); // SiteExtensions kernel.Bind <ISiteExtensionManager>().To <SiteExtensionManager>().InRequestScope(); // Command executor kernel.Bind <ICommandExecutor>().ToMethod(context => GetCommandExecutor(environment, context)) .InRequestScope(); MigrateSite(environment, noContextDeploymentsSettingsManager); RegisterRoutes(kernel, RouteTable.Routes); // Register the default hubs route: ~/signalr GlobalHost.DependencyResolver = new SignalRNinjectDependencyResolver(kernel); RouteTable.Routes.MapConnection <PersistentCommandController>("commandstream", "/commandstream"); RouteTable.Routes.MapHubs("/filesystemhub", new HubConfiguration()); }
// key goal is to create background tracer that is independent of request. public static async Task PerformBackgroundDeployment( DeploymentInfo deployInfo, IEnvironment environment, IDeploymentSettingsManager settings, TraceLevel traceLevel, Uri uri, bool waitForTempDeploymentCreation) { var tracer = traceLevel <= TraceLevel.Off ? NullTracer.Instance : new CascadeTracer(new XmlTracer(environment.TracePath, traceLevel), new ETWTracer(environment.RequestId, "POST")); var traceFactory = new TracerFactory(() => tracer); var backgroundTrace = tracer.Step(XmlTracer.BackgroundTrace, new Dictionary <string, string> { { "url", uri.AbsolutePath }, { "method", "POST" } }); // For monitoring creation of temp deployment var tcs = new TaskCompletionSource <object>(); // This task will be let out of scope intentionally var deploymentTask = Task.Run(() => { try { // lock related string lockPath = Path.Combine(environment.SiteRootPath, Constants.LockPath); string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); string statusLockPath = Path.Combine(lockPath, Constants.StatusLockFile); string hooksLockPath = Path.Combine(lockPath, Constants.HooksLockFile); var statusLock = new LockFile(statusLockPath, traceFactory); var hooksLock = new LockFile(hooksLockPath, traceFactory); var deploymentLock = new DeploymentLockFile(deploymentLockPath, traceFactory); var analytics = new Analytics(settings, new ServerConfiguration(), traceFactory); var deploymentStatusManager = new DeploymentStatusManager(environment, analytics, statusLock); var repositoryFactory = new RepositoryFactory(environment, settings, traceFactory); var siteBuilderFactory = new SiteBuilderFactory(new BuildPropertyProvider(), environment); var webHooksManager = new WebHooksManager(tracer, environment, hooksLock); var deploymentManager = new DeploymentManager(siteBuilderFactory, environment, traceFactory, analytics, settings, deploymentStatusManager, deploymentLock, NullLogger.Instance, webHooksManager); var fetchHandler = new FetchHandler(tracer, deploymentManager, settings, deploymentStatusManager, deploymentLock, environment, null, repositoryFactory); IDisposable tempDeployment = null; try { // Perform deployment deploymentLock.LockOperation(() => { ChangeSet tempChangeSet = null; if (waitForTempDeploymentCreation) { // create temporary deployment before the actual deployment item started // this allows portal ui to readily display on-going deployment (not having to wait for fetch to complete). // in addition, it captures any failure that may occur before the actual deployment item started tempDeployment = deploymentManager.CreateTemporaryDeployment( Resources.ReceivingChanges, out tempChangeSet, deployInfo.TargetChangeset, deployInfo.Deployer); tcs.TrySetResult(null); } fetchHandler.PerformDeployment(deployInfo, tempDeployment, tempChangeSet).Wait(); }, "Performing continuous deployment", TimeSpan.Zero); } catch (LockOperationException) { if (tempDeployment != null) { tempDeployment.Dispose(); } using (tracer.Step("Update pending deployment marker file")) { // REVIEW: This makes the assumption that the repository url is the same. // If it isn't the result would be buggy either way. FileSystemHelpers.SetLastWriteTimeUtc(fetchHandler._markerFilePath, DateTime.UtcNow); } } } catch (Exception ex) { tracer.TraceError(ex); } finally { backgroundTrace.Dispose(); } }); // When the frontend/ARM calls /deploy with isAsync=true, it starts polling // the deployment status immediately, so it's important that the temp deployment // is created before we return. if (waitForTempDeploymentCreation) { // If deploymentTask blows up before it creates the temp deployment, // go ahead and return. await Task.WhenAny(tcs.Task, deploymentTask); } }
public AsyncLockFileTests() { _lockFilePath = Path.Combine(PathHelper.TestLockPath, "file.lock"); _lockFile = new DeploymentLockFile(_lockFilePath, NullTracerFactory.Instance); _lockFile.InitializeAsyncLocks(); }
private static int Main(string[] args) { var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config")); // Turn flag on in app.config to wait for debugger on launch if (ConfigurationManager.AppSettings["WaitForDebuggerOnStart"] == "true") { while (!Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } } if (System.Environment.GetEnvironmentVariable(SettingsKeys.DisableDeploymentOnPush) == "1") { return(0); } if (args.Length < 2) { System.Console.WriteLine("Usage: kudu.exe appRoot wapTargets [deployer]"); return(1); } // The post receive hook launches the exe from sh and intereprets newline differently. // This fixes very wacky issues with how the output shows up in the console on push System.Console.Error.NewLine = "\n"; System.Console.Out.NewLine = "\n"; string appRoot = args[0]; string wapTargets = args[1]; string deployer = args.Length == 2 ? null : args[2]; string requestId = System.Environment.GetEnvironmentVariable(Constants.RequestIdHeader); IEnvironment env = GetEnvironment(appRoot, requestId); ISettings settings = new XmlSettings.Settings(GetSettingsPath(env)); IDeploymentSettingsManager settingsManager = new DeploymentSettingsManager(settings); // Setup the trace TraceLevel level = settingsManager.GetTraceLevel(); ITracer tracer = GetTracer(env, level); ITraceFactory traceFactory = new TracerFactory(() => tracer); // Calculate the lock path string lockPath = Path.Combine(env.SiteRootPath, Constants.LockPath); string deploymentLockPath = Path.Combine(lockPath, Constants.DeploymentLockFile); IOperationLock deploymentLock = DeploymentLockFile.GetInstance(deploymentLockPath, traceFactory); if (deploymentLock.IsHeld) { return(PerformDeploy(appRoot, wapTargets, deployer, lockPath, env, settingsManager, level, tracer, traceFactory, deploymentLock)); } // Cross child process lock is not working on linux via mono. // When we reach here, deployment lock must be HELD! To solve above issue, we lock again before continue. try { return(deploymentLock.LockOperation(() => { return PerformDeploy(appRoot, wapTargets, deployer, lockPath, env, settingsManager, level, tracer, traceFactory, deploymentLock); }, "Performing deployment", TimeSpan.Zero)); } catch (LockOperationException) { return(-1); } }