PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Bowlus/Source-Notify-Poll/Bowlus.AppHost/AppHostManager.cs

#
C# | 269 lines | 198 code | 31 blank | 40 comment | 32 complexity | 2fc07b659211133f62f4a66366e44059 MD5 | raw file
  1. namespace Bowlus.AppHost
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Security.AccessControl;
  9. using System.Threading;
  10. using Bowlus.Common;
  11. using Microsoft.Web.Administration;
  12. using Microsoft.WindowsAzure.ServiceRuntime;
  13. // Maintain the List of Applications Deployed to this Instance
  14. public class IISAppInfo
  15. {
  16. // i.e. 'test1.bowlus.net'
  17. public string HostHeader { get; set; }
  18. public string CurrentPath { get; set; }
  19. public DateTime Updated { get; set; }
  20. }
  21. // AppHost Manager
  22. public class AppHostManager
  23. {
  24. private readonly StorageAppRepository appRepository;
  25. // local root path for all sites
  26. private string rootSitePath;
  27. private string appCachePath;
  28. // provision a site with Staging Status == "provisioning"
  29. private bool provisionSite(TenantDeployment td)
  30. {
  31. // Unzip the site to the host folder
  32. var sitePath = Path.Combine(rootSitePath, td.HostName, DateTime.UtcNow.Ticks.ToString());
  33. appRepository.WriteAppTo(td.Application, sitePath);
  34. // provision in IIS config
  35. var siteName = RoleEnvironment.CurrentRoleInstance.Id + "_" + td.HostName;
  36. //<sites>
  37. // <site name="Bowlus.AppHost_IN_0_test1.thecloudsamurai.com" id="1">
  38. // <application path="/" applicationPool="test1">
  39. // <virtualDirectory path="/" physicalPath="C:\Resources\directory\37950156f46746a48bf597b89b5e832d.Bowlus.AppHost.Sites\test1.thecloudsamurai.com\634690683032291523\Site" />
  40. // </application>
  41. // <bindings>
  42. // <binding protocol="http" bindingInformation="10.115.48.42:8080:test1.thecloudsamurai.com" />
  43. // </bindings>
  44. // </site>
  45. //</sites>
  46. var iisApp = new IISAppInfo();
  47. iisApp.HostHeader = td.HostName;
  48. // contents in zip file is under 'Site' directory
  49. iisApp.CurrentPath = Path.Combine(sitePath, "Site");
  50. iisApp.Updated = new FileInfo(sitePath).LastWriteTimeUtc;
  51. // try 3 times
  52. for (int i = 0; ; i++)
  53. {
  54. try
  55. {
  56. // add site to IIS config
  57. lock (BowlusHelper.ServerManagerLock)
  58. {
  59. var sm = BowlusHelper.ServerManager;
  60. if (sm.Sites.Count(s => s.Name == siteName) > 0)
  61. {
  62. Trace.TraceWarning("Site already exists in IIS, however we still update and re-provision it." + siteName);
  63. var site = sm.Sites.SingleOrDefault(s => s.Name == (siteName));
  64. var hostHeaderParts = iisApp.HostHeader.Split('.');
  65. var appPoolName = hostHeaderParts.Length == 3 ? hostHeaderParts[0] : iisApp.HostHeader;
  66. site.Applications.First().ApplicationPoolName = GetAppPool(sm, appPoolName).Name;
  67. }
  68. else
  69. {
  70. var newSite = sm.Sites.Add(siteName, "http",
  71. RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Http"].IPEndpoint + ":" + iisApp.HostHeader, iisApp.CurrentPath);
  72. var hostHeaderParts = iisApp.HostHeader.Split('.');
  73. var appPoolName = hostHeaderParts.Length == 3 ? hostHeaderParts[0] : iisApp.HostHeader;
  74. newSite.Applications.First().ApplicationPoolName = GetAppPool(sm, appPoolName).Name;
  75. }
  76. sm.CommitChanges();
  77. }
  78. break;
  79. }
  80. catch (Exception ex)
  81. {
  82. Trace.TraceError(ex.Message + '\n' + ex.StackTrace);
  83. if (i == 3) throw;
  84. Thread.Sleep(3000);
  85. }
  86. }
  87. // update td status
  88. td.StagingId = RoleEnvironment.CurrentRoleInstance.Id;
  89. td.SStatus = TenantDeployment.S_PROVISIONED;
  90. TentantDepTableContext.Instance.UpdateTD(td);
  91. Trace.TraceInformation("Site provisioned: " + siteName);
  92. return true;
  93. }
  94. // deprovision a site with Staging Status == "deprovisioning"
  95. private bool deprovisionSite(TenantDeployment td)
  96. {
  97. // delete from IIS config
  98. var siteName = RoleEnvironment.CurrentRoleInstance.Id + "_" + td.HostName;
  99. if (BowlusHelper.ServerManager.Sites.Count(s => s.Name == siteName) == 0)
  100. {
  101. Trace.TraceWarning("Cannot deprovision from IIS a site does not exist. " + siteName);
  102. }
  103. else
  104. {
  105. // try 3 times
  106. for (int i = 0; ; i++)
  107. {
  108. try
  109. {
  110. // real logic, to be tried
  111. lock (BowlusHelper.ServerManagerLock)
  112. {
  113. var sm = BowlusHelper.ServerManager;
  114. var site = sm.Sites.SingleOrDefault(s => s.Name == (siteName));
  115. var appPoolName = site.Applications[0].ApplicationPoolName;
  116. var appPool = sm.ApplicationPools.SingleOrDefault(ap => ap.Name == appPoolName);
  117. sm.ApplicationPools.Remove(appPool);
  118. sm.Sites.Remove(site);
  119. sm.CommitChanges();
  120. }
  121. break;
  122. }
  123. catch (Exception ex)
  124. {
  125. Trace.TraceError(ex.Message + '\n' + ex.StackTrace);
  126. if (i == 3) throw;
  127. Thread.Sleep(3000);
  128. }
  129. }
  130. }
  131. // delete from IIS server file directory
  132. var directory = Directory.EnumerateDirectories(rootSitePath).SingleOrDefault(d => d.Contains(td.HostName));
  133. Directory.Delete(directory, true);
  134. // delete from tenant deployment table
  135. TentantDepTableContext.Instance.DeleteTD(td);
  136. Trace.TraceInformation("Site deprovisioned: " + siteName);
  137. return true;
  138. }
  139. // sync can be safely called any times and at anytime.
  140. public int Sync()
  141. {
  142. var ctx = TentantDepTableContext.Instance;
  143. string localIpv4Address = BowlusHelper.GetHostIpAddress();
  144. int changes = 0;
  145. var q = (from td in ctx.TDQueryable
  146. where td.SStatus == TenantDeployment.S_PROVISIONING && td.Staging == localIpv4Address
  147. select td).ToList<TenantDeployment>();
  148. foreach (var td in q)
  149. {
  150. try
  151. {
  152. if (provisionSite(td)) changes++;
  153. }
  154. catch (Exception ex)
  155. {
  156. Trace.TraceError("Provision fail: "+ td.HostName + ", " + ex.Message);
  157. if (RoleEnvironment.IsEmulated)
  158. throw;
  159. }
  160. }
  161. q = (from td in ctx.TDQueryable
  162. // where td.SStatus == TenantDeployment.S_DEPROVISIONING && td.Staging == localIpv4Address
  163. where td.SStatus == TenantDeployment.S_DEPROVISIONING && td.InstanceId == RoleEnvironment.CurrentRoleInstance.Id
  164. select td).ToList<TenantDeployment>();
  165. foreach (var td in q)
  166. {
  167. try
  168. {
  169. if (deprovisionSite(td)) changes++;
  170. }
  171. catch (Exception ex)
  172. {
  173. Trace.TraceError("Deprovision fail: " + td.HostName + ", " + ex.Message);
  174. if (RoleEnvironment.IsEmulated)
  175. throw;
  176. }
  177. }
  178. return changes;
  179. }
  180. // get, or create if not exist, application pool for the site
  181. private static ApplicationPool GetAppPool(ServerManager sm, string siteName)
  182. {
  183. var appPool = sm.ApplicationPools.SingleOrDefault(p => p.Name == siteName);
  184. if (appPool == null)
  185. {
  186. appPool = sm.ApplicationPools.Add(siteName);
  187. appPool.ManagedRuntimeVersion = "v4.0";
  188. appPool.ProcessModel.LoadUserProfile = false;
  189. }
  190. return appPool;
  191. }
  192. // Configure the directory security.
  193. private static void ConfigureDirectorySecurity(string rootPath, string path)
  194. {
  195. var sec = Directory.GetAccessControl(rootPath);
  196. sec.AddAccessRule(new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
  197. Directory.SetAccessControl(path, sec);
  198. }
  199. // set appRepository and serverManager
  200. public AppHostManager(StorageAppRepository repository)
  201. {
  202. appRepository = repository;
  203. }
  204. // 1) init sitesPath and appCachePath
  205. // 2) provision active sites
  206. // init will be called at a later time than AppHostManager constructor, when the VM is ready.
  207. public void Initialize()
  208. {
  209. Trace.TraceInformation("Initialize AppHostManager");
  210. try
  211. {
  212. // Initialize Local Resources
  213. this.rootSitePath = RoleEnvironment.GetLocalResource("Sites").RootPath.TrimEnd('\\');
  214. this.appCachePath = RoleEnvironment.GetLocalResource("AppCache").RootPath.TrimEnd('\\');
  215. // Configure Security on our directories
  216. ConfigureDirectorySecurity(this.rootSitePath, this.rootSitePath);
  217. ConfigureDirectorySecurity(this.rootSitePath, this.appCachePath);
  218. // try to provision active site.
  219. // this will only happen when the VM starts, while the TenantDeployment table 'Active' sites
  220. // are not in VM. Because the deployment table persists while VMs don't.
  221. var ctx = TentantDepTableContext.Instance;
  222. var q = (from td in ctx.TDQueryable
  223. where td.Status == TenantDeployment.S_ACTIVE && td.InstanceId == RoleEnvironment.CurrentRoleInstance.Id
  224. select td).ToList<TenantDeployment>();
  225. foreach (var td in q)
  226. {
  227. provisionSite(td);
  228. }
  229. Sync();
  230. }
  231. catch (Exception ex)
  232. {
  233. Trace.TraceError(ex.Message + '\n' + ex.StackTrace);
  234. throw;
  235. }
  236. }
  237. }
  238. }