PageRenderTime 62ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/Accelerator/Application.cs

https://bitbucket.org/zgramana/azure-accelerators-project
C# | 1199 lines | 1020 code | 55 blank | 124 comment | 51 complexity | 903bc3942062232840240c2bf3fe715f MD5 | raw file
Possible License(s): LGPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Text.RegularExpressions;
  8. using System.Threading;
  9. using System.Xml.Linq;
  10. using System.Xml.XPath;
  11. using Microsoft.Synchronization;
  12. using Microsoft.Web.Administration;
  13. using Microsoft.WindowsAzure.Accelerator.Diagnostics;
  14. using Microsoft.WindowsAzure.Accelerator.Synchronization;
  15. using Microsoft.WindowsAzure.ServiceRuntime;
  16. using Microsoft.WindowsAzure.StorageClient;
  17. using Microsoft.WindowsAzure.Diagnostics;
  18. using Timer = System.Threading.Timer;
  19. using Path = System.IO.Path;
  20. namespace Microsoft.WindowsAzure.Accelerator
  21. {
  22. /// <summary>
  23. /// Manages the configuration and execution of an application and its dependencies.
  24. /// </summary>
  25. public class Application
  26. {
  27. #region | PROPERTIES
  28. public String Name { get { return XAppConfig.GetAttribute("name"); } }
  29. public String Version { get { return XAppConfig.GetAttribute("version"); } }
  30. public List<Application> ChildApplications { get; private set; }
  31. public List<SyncManager> BlobSyncInstances { get; private set; }
  32. public Dictionary<String, Process> Processes { get; private set; }
  33. public Dictionary<String, LocalResource> LocalResources { get; private set; }
  34. public Dictionary<String, CloudStorageAccount> Accounts { get; private set; }
  35. public Dictionary<String, Variable> Variables { get { return _variables; } }
  36. public Dictionary<String, Microsoft.Web.Administration.Site> Sites { get { return _sites; } }
  37. private XElement XAppConfig { get; set; }
  38. private XElement Config { get { return XAppConfig.Element("configuration"); } }
  39. private XElement Dependencies { get { return XAppConfig.Element("dependencies"); } }
  40. private IEnumerable<XElement> Params { get { return XAppConfig.Descendants("param"); } }
  41. private IEnumerable<XElement> Vars { get { return XAppConfig.Descendants("variable"); } }
  42. private readonly Dictionary<String, Variable> _variables = new Dictionary<String, Variable>();
  43. private readonly Dictionary<String, Site> _sites = new Dictionary<String, Site>();
  44. #endregion
  45. #region | CONSTRUCTOR
  46. /// <summary>
  47. /// Initializes a new instance of the <see cref="Application"/> class.
  48. /// </summary>
  49. /// <remarks>
  50. /// Variables / Params:
  51. ///
  52. /// Variables overwrite existing keys (whereas parameters are inherited; and do not). This allows parent
  53. /// applications the ability to override parameter values in constituent child applications.
  54. /// For example, an application may have IIS and PHP as dependant child applications. The params pointing
  55. /// to locations and paths are specific to the application. The application can override any param
  56. /// declared in IIS or PHP by simply declaring its own param of the same name/key.
  57. ///
  58. /// Processing Order:
  59. ///
  60. /// Params, variables, and process tear-down are the only 3 occasions where the parent applications goes first.
  61. /// In all other instances of provisioning resource, configuration, and application start orchestration; it is
  62. /// the child applications who perform their actions prior to the parent applications.
  63. /// </remarks>
  64. /// <param name="applicationConfigurationSection">The application configuration xml.</param>
  65. public Application(XElement applicationConfigurationSection)
  66. {
  67. XAppConfig = applicationConfigurationSection;
  68. LogLevel.Information.Trace(Name, "Initialization : Starting...");
  69. LogLevel.Verbose.TraceContent(Name, XAppConfig.ToString(), "Initialization : Definition :");
  70. LocalResources = new Dictionary<String, LocalResource>();
  71. Accounts = new Dictionary<String, CloudStorageAccount>();
  72. Processes = new Dictionary<String, Process>();
  73. BlobSyncInstances = new List<SyncManager>();
  74. ChildApplications = new List<Application>();
  75. //i|
  76. //i| Process paramerters.
  77. //i|
  78. LogLevel.Information.Trace(Name, "Initialization : Parameters : Loading.");
  79. foreach ( XElement parameter in Params )
  80. {
  81. //i|
  82. //i| Parameters are disregarded if they have already been set by a loaded application.
  83. //i|
  84. if (parameter.IsEnabled()) ProcessVariableElement(parameter, true);
  85. }
  86. LogLevel.Information.Trace(Name, "Initialization : Parameters : Loaded.");
  87. //i|
  88. //i| Process variables.
  89. //i|
  90. LogLevel.Information.Trace(Name, "Initialization : Variables : Loading...");
  91. foreach ( XElement variable in Vars )
  92. {
  93. //i|
  94. //i| Variables overwrite existing keys (whereas parameters are inherited; and do not).
  95. //i|
  96. if (variable.IsEnabled()) ProcessVariableElement(variable, false);
  97. }
  98. LogLevel.Information.Trace(Name, "Initialization : Variables : Loaded.");
  99. //i|
  100. //i| Load any child applications required by this application.
  101. //i|
  102. foreach ( var a in XAppConfig.XPathSelectElements("dependencies/applications/application").ToList())
  103. {
  104. if ( a.IsEnabled() )
  105. {
  106. XElement cs = ServiceManager.GetApplicationConfigurationSection(XAppConfig.Parent.Document, a.GetAttribute("name"), a.GetAttribute("version"));
  107. if (!ServiceManager.IsExistingApplication(cs.GetAttribute("name"), cs.GetAttribute("version")))
  108. ChildApplications.Add(new Application(cs));
  109. }
  110. }
  111. LogLevel.Information.Trace(Name, "Initialization : Completed.");
  112. }
  113. /// <summary>
  114. /// Determines whether an instance of the application exists as a dependant application.
  115. /// </summary>
  116. /// <param name="name">The name.</param>
  117. /// <param name="version">The version.</param>
  118. public Boolean IsChildApplication(String name, String version)
  119. {
  120. foreach ( var a in ChildApplications )
  121. if ( ( a.Name == name && a.Version == version ) || a.IsChildApplication(name, version) )
  122. return true;
  123. return false;
  124. }
  125. #endregion
  126. #region | EVENTS
  127. /// <summary>
  128. /// Performs initialization and configuration of resources for this and all dependant applications.
  129. /// </summary>
  130. public void OnStart()
  131. {
  132. //i| Process all dependant application's dependencies and configuration first.
  133. foreach ( Application application in ChildApplications )
  134. application.OnStart();
  135. //i| Process application dependencies.
  136. if ( Dependencies != null && Dependencies.HasElements )
  137. ProcessDependencies();
  138. //i| Process application configuration.
  139. if ( Config != null && Config.HasElements )
  140. ProcessConfiguration();
  141. }
  142. /// <summary>
  143. /// Manages the execution of process and dependant processes for the configured accelerator application instance.
  144. /// </summary>
  145. public void OnRun()
  146. {
  147. //i| Process dependant applications first.
  148. foreach (Application application in ChildApplications)
  149. application.OnRun();
  150. //i| Launch all defined processes for this application.
  151. foreach ( XElement e in XAppConfig.XPathSelectElements("runtime/onStart/process").ToList() )
  152. if ( e.IsEnabled() ) ProcessStartElement(e);
  153. //i| Launch hosted web servers.
  154. foreach ( XElement e in XAppConfig.XPathSelectElements("runtime/onStart/webServer").ToList() )
  155. if ( e.IsEnabled() ) ProcessWebServerElement(e);
  156. //i| Process blob to local storage synch
  157. foreach (XElement e in XAppConfig.XPathSelectElements("runtime/onStart/cloudSync").ToList())
  158. if (e.IsEnabled()) ProcessBlobSyncElement(e);
  159. }
  160. /// <summary>
  161. /// Manages the running components.
  162. /// </summary>
  163. public void OnRunning()
  164. {
  165. //i| Process dependant applications first.
  166. foreach (Application application in ChildApplications )
  167. application.OnRunning();
  168. //i| Process and runtime application timers.
  169. foreach ( XElement e in XAppConfig.XPathSelectElements("runtime/onRunning/timer").ToList() )
  170. if ( e.IsEnabled() ) ProcessTimerElement(e);
  171. //i| Process blob to local storage synch
  172. foreach (XElement e in XAppConfig.XPathSelectElements("runtime/onRunning/cloudSync").ToList())
  173. if (e.IsEnabled()) ProcessBlobSyncElement(e);
  174. }
  175. /// <summary>
  176. /// Performs the tear down of application environment and configuration files using Azure runtime service values.
  177. /// </summary>
  178. public void OnStop()
  179. {
  180. //i| Start all configuration 'graceful' tear-down processes.
  181. foreach ( XElement e in XAppConfig.XPathSelectElements("runtime/onStop/process").ToList() )
  182. if ( e.IsEnabled() ) ProcessStartElement(e);
  183. //i| Kill any processes we started that still remain.
  184. foreach ( var process in Processes.Values)
  185. if ( process != null && !process.HasExited )
  186. process.Protect(p => p.Kill());
  187. Processes.Clear();
  188. //i| Abort any blob synchronization timers.
  189. foreach ( var blobSync in BlobSyncInstances )
  190. blobSync.Stop();
  191. BlobSyncInstances.Clear();
  192. //i| Finally, do the same for each of our dependant applications.
  193. foreach ( Application application in ChildApplications )
  194. application.OnStop();
  195. }
  196. /// <summary>
  197. /// Performs the intialization of applications dependencies and resources.
  198. /// </summary>
  199. private void ProcessDependencies()
  200. {
  201. LogLevel.Information.Trace(Name, "Load Dependencies : Starting...");
  202. //i| Process local file storage and cache.
  203. foreach ( XElement e in Dependencies.Descendants("localStorage") )
  204. if (e.IsEnabled()) ProcessLocalStorageElement(e);
  205. //i| Process provisioned Endpoints
  206. foreach ( XElement e in Dependencies.Descendants("endPoint") )
  207. if (e.IsEnabled()) ProcessEndPointElement(e);
  208. //i| Process azure cloud drives.
  209. foreach ( XElement e in Dependencies.Descendants("cloudDrive") )
  210. if (e.IsEnabled()) ProcessCloudDriveElement(e);
  211. //i| Process blob to local storage synch
  212. foreach (XElement e in Dependencies.XPathSelectElements("cloudSync").ToList())
  213. if (e.IsEnabled()) ProcessBlobSyncElement(e);
  214. //i| Process local file copy elements (eg. read-only AppRole files to local storage)
  215. foreach ( XElement e in Dependencies.Descendants("localCache") )
  216. if (e.IsEnabled()) ProcessLocalCopyElement(e);
  217. //i| Perform file validation.
  218. foreach ( XElement e in Dependencies.Descendants("fileValidation") )
  219. if (e.IsEnabled()) ProcessFileValidationElement(e);
  220. LogLevel.Information.Trace(Name, "Load Dependencies : Completed.");
  221. }
  222. /// <summary>
  223. /// Performs the initialization of application environment and configuration files using Azure runtime service values.
  224. /// </summary>
  225. private void ProcessConfiguration()
  226. {
  227. LogLevel.Information.Trace(Name, "Configuration : Starting...");
  228. //i| Process environment variables.
  229. foreach ( XElement e in Config.Descendants("environmentVariable") )
  230. if (e.IsEnabled()) ProcessEnvironmentVariableElement(e);
  231. //i| Launch site web servers.
  232. foreach (XElement e in Config.XPathSelectElements("site").ToList())
  233. if (e.IsEnabled()) e.Protect(se => ProcessSiteElement(se));
  234. //i| Process configuration files.
  235. foreach (XElement e in Config.XPathSelectElements("files/file").ToList())
  236. if (e.IsEnabled()) ProcessFileConfigurationElement(e);
  237. LogLevel.Information.Trace(Name, "Configuration : Completed.");
  238. }
  239. #endregion
  240. #region | CONFIGURATION
  241. /// <summary>
  242. /// Processes the variable element.
  243. /// </summary>
  244. /// <param name="element">The element.</param>
  245. /// <param name="isParam">if set to <c>true</c> is param.</param>
  246. private static void ProcessVariableElement(XElement element, Boolean isParam)
  247. {
  248. String key = element.GetAttribute("key");
  249. String value = element.GetAttribute("value");
  250. //i| Parameters which are already set are not evaluated. This allows parent applications
  251. //i| the ability to override this class of variable in constituent dependant apps.
  252. if ( String.IsNullOrEmpty(value) || ( ServiceManager.Variables.ContainsKey(key) && isParam ) )
  253. return;
  254. ServiceManager.Variables[key] = value;
  255. }
  256. /// <summary>
  257. /// Processes the timer element for OnRunning interval processes (such as custom log collection to upload folder).
  258. /// </summary>
  259. /// <param name="element">The timer configuration element.</param>
  260. private void ProcessTimerElement(XElement element)
  261. {
  262. var config = new
  263. {
  264. TimerName = (String) new Variable(element.GetAttribute("name")),
  265. IntervalSeconds = (Int32) new Variable(element.GetAttribute("intervalInSeconds")),
  266. Processes = element.Descendants("process").OnValid(e => e.Where(p => p.IsEnabled()).ToList())
  267. };
  268. if ( config.Processes == null )
  269. return;
  270. LogLevel.Information.Trace(Name, "Running() : Creating interval processes execution timer : {{{{ Name: '{0}' }}, {{ IntervalSeconds: '{1}' }}}}.", config.TimerName, config.IntervalSeconds);
  271. new Timer(timerObject =>
  272. {
  273. LogLevel.Information.Trace(Name, "Running() : Interval process triggered : {{ '{0}' }}.", config.TimerName);
  274. foreach (XElement process in config.Processes)
  275. if (process.IsEnabled()) ProcessStartElement(process);
  276. },
  277. config,
  278. config.IntervalSeconds * 1000,
  279. config.IntervalSeconds * 1000);
  280. }
  281. /// <summary>
  282. /// Starts a process based on a process element configuration.
  283. /// </summary>
  284. /// <param name="element">The process configuration element.</param>
  285. private void ProcessStartElement(XElement element)
  286. {
  287. String processKey = new Variable(element.GetAttribute("processKey"));
  288. String command = Environment.ExpandEnvironmentVariables(new Variable(element.GetAttribute("command")));
  289. String args = Environment.ExpandEnvironmentVariables(new Variable(element.GetAttribute("args")));
  290. String workingDir = Environment.ExpandEnvironmentVariables(new Variable(element.GetAttribute("workingDir")));
  291. Boolean waitOnExit = new Variable(element.GetAttribute("waitOnExit") ?? "false");
  292. Int32 waitTimeoutInSeconds = new Variable(element.GetAttribute("waitTimeoutInSeconds") ?? "0");
  293. Int32 delayContinueInSeconds = element.AttributeAsVariable("delayContinueInSeconds", "0");
  294. LogLevel.Information.Trace(
  295. Name,
  296. "Process : Creating Process : {{{{ {0} }}, {{ '{1} {2}' }}, {{ WorkingDirectory: '{3}' }}, {{ DelaySeconds: '{4}' }}}}.",
  297. processKey,
  298. command,
  299. args,
  300. workingDir,
  301. delayContinueInSeconds
  302. );
  303. Processes[processKey] = RunProcess(processKey, command, workingDir, args, waitOnExit, waitTimeoutInSeconds);
  304. LogLevel.Information.Trace(Name, "Process : {0} : Waiting for '{1}' seconds before continue...", processKey, delayContinueInSeconds);
  305. Thread.Sleep(delayContinueInSeconds * 1000);
  306. }
  307. /// <summary>
  308. /// Processes the IIS site element (full IIS support).
  309. /// </summary>
  310. /// <param name="element">The configuration element.</param>
  311. private void ProcessSiteElement(XElement element)
  312. {
  313. String siteNameKey = new Variable(element.GetAttribute("siteNameKey", "SiteName"));
  314. String virtualPathKey = new Variable(element.GetAttribute("virtualPath", "VirtualPath"));
  315. String physicalPathKey = new Variable(element.GetAttribute("physicalPath", "PhysicalPath"));
  316. String physicalFolderNameKey = new Variable(element.GetAttribute("physicalFolderNameKey", "PhysicalFolderName"));
  317. String appPoolKey = new Variable(element.GetAttribute("appPoolKey", "AppPoolName"));
  318. String siteFolderKey = new Variable(element.GetAttribute("siteFolderKey", "SiteFolderName"));
  319. RuntimeVersion runtimeVersion = element.GetAttribute("runtime").ToEnum<RuntimeVersion>() ?? RuntimeVersion.v2;
  320. Boolean enableClassicPipelineMode = new Variable(element.GetAttribute("enableClassicPipelineMode") ?? "false");
  321. ServiceManager.Variables["replacementSitePath"] = new Variable(element.GetAttribute("replacementSitePath", "$(IisDrive)\\$(SiteName)$(VirtualPath)"));
  322. String replacePathChar = new Variable(element.GetAttribute("replacePathChar") ?? ".");
  323. //i|
  324. using (ServerManager sm = new ServerManager())
  325. {
  326. foreach(Site s in sm.Sites.Where(site => site.Name.StartsWith(RoleEnvironment.CurrentRoleInstance.Id)))
  327. {
  328. //i|
  329. //i| Populate variables which will be used by file verification and custom processes.
  330. //i|
  331. ServiceManager.Variables[siteNameKey] = s.Name.Replace(RoleEnvironment.CurrentRoleInstance.Id + "_", "");
  332. //i|
  333. //i| Update host bindings
  334. //i|
  335. foreach (Binding b in s.Bindings)
  336. {
  337. if (b.IsIPPortHostBinding)
  338. {
  339. ServiceManager.HostBindings[b.Host] = b.EndPoint.Address;
  340. }
  341. }
  342. //i|
  343. //i| Perform file validation.
  344. //i|
  345. Boolean valid = true;
  346. element.Descendants("fileValidation").Where(fv => fv.IsEnabled()).ForEach(fv => valid = valid && ProcessFileValidationElement(fv));
  347. if (!valid)
  348. {
  349. LogLevel.Warning.Trace(Name, "Site : Cannot find site file match; aborting site config.");
  350. }
  351. else
  352. {
  353. LogLevel.Information.Trace(Name, "Configuring site [{0}].", ServiceManager.Variables[siteNameKey]);
  354. //i|
  355. //i| Traverse site application and physical directories.
  356. //i|
  357. foreach (var a in s.Applications)
  358. {
  359. ServiceManager.Variables[appPoolKey] = a.ApplicationPoolName; //i| Set the variable for each application.
  360. ServiceManager.Variables[virtualPathKey] = a.Path;
  361. LogLevel.Information.Trace(Name, "Configuring application pool [{0}].", ServiceManager.Variables[appPoolKey]);
  362. ApplicationPool ap = sm.ApplicationPools[a.ApplicationPoolName];
  363. //i|
  364. //i| Update ASP.NET version: only if 4.0 (otherwise its simply the absence thereof)
  365. //i|
  366. LogLevel.Information.Trace(Name, "Managed runtime version is [{0}].", ap.ManagedRuntimeVersion);
  367. if (runtimeVersion.ToAppHostVersionString() != ap.ManagedRuntimeVersion)
  368. {
  369. LogLevel.Information.Trace(Name, "Setting runtime version [{0}] -> [{1}].", ap.ManagedRuntimeVersion, runtimeVersion.ToAppHostVersionString() );
  370. ap.ManagedRuntimeVersion = runtimeVersion.ToAppHostVersionString();
  371. }
  372. //i|
  373. //i| Update classic pipeline mode.
  374. //i|
  375. LogLevel.Information.Trace(Name, "Managed pipeline mode is [{0}].", ap.ManagedPipelineMode.ToString());
  376. if (enableClassicPipelineMode && (ap.ManagedPipelineMode != ManagedPipelineMode.Classic))
  377. {
  378. LogLevel.Information.Trace(Name, "Setting pipeline mode [{0}] -> [{1}]", ManagedPipelineMode.Classic.ToString());
  379. ap.ManagedPipelineMode = ManagedPipelineMode.Classic;
  380. }
  381. //i|
  382. //i| Move physical directories.
  383. //i|
  384. String parentVirtualPath = ServiceManager.Variables[virtualPathKey];
  385. foreach (var v in a.VirtualDirectories)
  386. {
  387. String physicalPath = v.PhysicalPath;
  388. try
  389. {
  390. String relativeVirtualPath = Path.Combine(a.Path, v.Path);
  391. String relativePhysicalPath = relativeVirtualPath.Replace("//", "~").Replace("/", ".");
  392. ServiceManager.Variables[virtualPathKey] = relativePhysicalPath; // (parentVirtualPath.EnsureEndsWith("/") + v.Path).Replace(@"//", @"~").Replace("/", replacePathChar).EnsureStartsWith("~");
  393. ServiceManager.Variables[physicalPathKey] = physicalPath;
  394. ServiceManager.Variables[siteFolderKey] = Directory.GetParent(v.PhysicalPath).Name;
  395. Path replacement = ServiceManager.Variables["replacementSitePath"].ToString().SetPathChar('\\');
  396. LogLevel.Information.Trace(Name, @"Redirecting Virtual Directory [{0}] from [{1}] -> [{2}]", ServiceManager.Variables[virtualPathKey], ServiceManager.Variables[physicalPathKey], replacement);
  397. if (!Directory.Exists(replacement))
  398. {
  399. LogLevel.Warning.Trace(Name, @"Target directory does not exist [{0}].", replacement);
  400. LogLevel.Information.Trace(Name, @"Creating target directory and migrating solution files.");
  401. Path commandPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Scripts\copySite.bat");
  402. String args = string.Format(@"""{0}"" ""{1}"" {2} /onlyOnEmpty", physicalPath, replacement, ServiceManager.Variables[appPoolKey]);
  403. ConsoleProcess.Start(commandPath, ".", true, args);
  404. //ConsoleProcess.CopyFolder(physicalPath, replacement);
  405. LogLevel.Information.Trace(Name, @"Web site migration complete.");
  406. }
  407. LogLevel.Information.Trace(Name, @"Redirecting [{0}] -> [{1}]", ServiceManager.Variables[physicalPathKey], replacement);
  408. v.PhysicalPath = v.PhysicalPath.Replace(physicalPath, replacement);
  409. }
  410. catch (Exception ex)
  411. {
  412. LogLevel.Error.TraceException(Name, ex, "An exception occured moving the vitual directory: Site will be run from existing solution folder.");
  413. v.PhysicalPath = physicalPath;
  414. }
  415. try
  416. {
  417. //i| Process site configuration files.
  418. foreach (XElement e in element.XPathSelectElements("files/file").ToList())
  419. if (e.IsEnabled()) ProcessFileConfigurationElement(e);
  420. //i|
  421. //i| Run customized site processes.
  422. //i|
  423. foreach (XElement process in element.Descendants("process").Where(e => e.IsEnabled()))
  424. {
  425. //x| RunProcess("copySite", commandPath, ".", args, true, 0);
  426. LogLevel.Information.Trace(Name, "Starting application specific configuration process.");
  427. ProcessStartElement(process);
  428. }
  429. }
  430. catch (Exception ex)
  431. {
  432. LogLevel.Error.TraceException(Name, ex, "An exception occured when running the configuration process.");
  433. }
  434. }
  435. }
  436. }
  437. }
  438. LogLevel.Information.Trace(Name, "Commiting changes to IIS.");
  439. sm.CommitChanges();
  440. ServiceManager.Variables["HostBindings"] = String.Concat(ServiceManager.HostBindings.Select(b => String.Format("\n{0} {1}", b.Value, b.Key)).ToArray());
  441. }
  442. }
  443. /// <summary>
  444. /// Processes the web server element (hosted web core). The hosted web core is started by the
  445. /// Service Manager after all defined applications have processed their individual OnStart
  446. /// definitions.
  447. /// </summary>
  448. /// <param name="element">The configuration element.</param>
  449. private static void ProcessWebServerElement(XElement element)
  450. {
  451. if ( new Variable(element.GetAttribute("enablePhp") ?? "false") )
  452. {
  453. //b| NOTE:
  454. //b|
  455. //b| This comment belongs elsewhere, but I am writing it now and don't want to burn
  456. //b| bandwidth to figure out where it belongs. So, please understand that the comment
  457. //b| below is much larger in breadth and scope than the place at which I am typing
  458. //b| it would reasonably indicate. (i|rdm)
  459. //b|
  460. //i| If true, the PHPRC environment variable must be set to the PHP home directory
  461. //i| prior to runtime start of the hosted web core. The easiest way to do this
  462. //i| is to make the PHP application definition a child dependency of the hosted web
  463. //i| core application.
  464. //i|
  465. //i| This is done is serveral sample apps such as Wordpress (via IIS); where the
  466. //i| the definition includes IIS and PHP dependencies; and then sets 'enablePhp'
  467. //i| to true in its own web core definition. (Its web core definitions extends the
  468. //i| base definitions provided by including the aforementioned child IIS dependency.)
  469. //i|
  470. //i| Of course, the Wordpress (via IIS) application section could declare all of the
  471. //i| IIS (webcore) and PHP related resources in its own definition; and forego any
  472. //i| child dependecies, but whats the fun in that? (Or the reusability?)
  473. //i|
  474. //i| Breaking out the definitions for applications such as IIS and PHP enable them
  475. //i| to be used in multiple instances (such as hosting dozens of ASP.NET apps) in a
  476. //i| clean and modular fashion. With definitions building upon themselves and playing
  477. //i| well together. (Or at least as well as I can in 5 weeks worth of work.)
  478. //i|
  479. //i| Wordpress (via Apache) would declare Apache and PHP dependencies; and would start
  480. //i| the Apache httpd process (and not declare a web core element at all).
  481. ServiceManager.WebServer.IsPhpEnabled = true;
  482. }
  483. //i| Once the value has been set to true (requiring a Classic Mode pipeline), it remains true.
  484. //i| This prevents other dependant, parent or separate applications from removing this resource
  485. //i| after initially requested.
  486. ServiceManager.WebServer.IsClassicModeEnabled = new Variable(element.GetAttribute("enableClassicPipelineMode") ?? "false");
  487. //i| Add all web applications (they will all share the same application pool.) There is only
  488. //i| one hosted web core per, but it may host an indefinite number of applications ( using
  489. //i| the same appplication pool).
  490. foreach (XElement e in element.Elements("application"))
  491. ServiceManager.WebServer.AddApplication(
  492. new Variable(e.GetAttribute("applicationPath")),
  493. new Variable(e.GetAttribute("physicalPath")),
  494. new Variable(e.GetAttribute("virtualDirectory") ?? "/")
  495. );
  496. //i| Add all unique bindings. ( Any duplicates bindings added by this or other active applications'
  497. //i| definitions will simply be ignored, )
  498. foreach ( XElement e in element.Elements("binding") )
  499. ServiceManager.WebServer.Bindings.Add(
  500. new WebServerBinding
  501. {
  502. Address = new Variable(e.GetAttribute("address")),
  503. Port = new Variable(e.GetAttribute("port")),
  504. HostHeader = new Variable(e.GetAttribute("host")),
  505. Protocol = new Variable(e.GetAttribute("protocol") ?? "Http").ToString().ToEnum<WebServerBinding.ProtocolType>()
  506. }
  507. );
  508. }
  509. /// <summary>
  510. /// Processes the local storage element.
  511. /// </summary>
  512. /// <param name="element">The element.</param>
  513. private void ProcessLocalStorageElement(XElement element)
  514. {
  515. String resourceName = new Variable(element.GetAttribute("configurationResourceName"));
  516. String pathKey = new Variable(element.GetAttribute("pathKey"));
  517. String maxSizeKey = new Variable(element.GetAttribute("maximumSizeKey"));
  518. LocalResource storage = RoleEnvironment.GetLocalResource(resourceName);
  519. LocalResources.Add(pathKey, storage);
  520. ServiceManager.Variables[pathKey] = storage.RootPath.TrimEnd('\\', '/', ' ');
  521. ServiceManager.Variables[maxSizeKey] = storage.MaximumSizeInMegabytes.ToString();
  522. LogLevel.Information.Trace(Name, "LocalStorage : Loaded : {{{{ Name: '{0}' }}, {{ Path, '{1}' }}, {{ Size: '{2}' }}}}.", resourceName, ServiceManager.Variables[pathKey], ServiceManager.Variables[maxSizeKey]);
  523. }
  524. /// <summary>
  525. /// Processes the end point element.
  526. /// </summary>
  527. /// <param name="element">The element.</param>
  528. private void ProcessEndPointElement(XElement element)
  529. {
  530. String endPointName = new Variable(element.GetAttribute("configurationEndPointName"));
  531. String portKey = new Variable(element.GetAttribute("portKey") ?? ( element.GetAttribute("configurationEndPointName") + "Port" ));
  532. String addressKey = new Variable(element.GetAttribute("addressKey") ?? ( element.GetAttribute("configurationEndPointName") + "Address" ));
  533. IPEndPoint localEndPoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints[endPointName].IPEndpoint;
  534. ServiceManager.Variables[portKey] = localEndPoint.Port.ToString();
  535. ServiceManager.Variables[addressKey] = localEndPoint.Address.ToString();
  536. LogLevel.Information.Trace(Name, "EndPoint : Loaded : {{{{ Name: '{0}' }}, {{ Address: '{1}' }}, {{ Port: '{2}' }}}}.", endPointName, ServiceManager.Variables[addressKey], ServiceManager.Variables[portKey]);
  537. }
  538. /// <summary>
  539. /// Processes the cloud drive element.
  540. /// </summary>
  541. /// <param name="element">The element.</param>
  542. private void ProcessCloudDriveElement(XElement element)
  543. {
  544. String pathKey = new Variable(element.GetAttribute("pathKey"));
  545. String connectionString = new Variable(element.GetAttribute("connectionString", "$(AcceleratorConnectionString)"));
  546. String pageBlobUri = new Variable(element.GetAttribute("pageBlobUri", "$(AcceleratorDrivePageBlobUri)"));
  547. Boolean createIfNotExists = new Variable(element.GetAttribute("createIfNotExist", "true"));
  548. Int32 createSizeInMB = new Variable(element.GetAttribute("createSizeInMB", "2048"));
  549. Int32 cacheSizeInMB = new Variable(element.GetAttribute("cacheSizeInMB", "1024"));
  550. Boolean readOnly = new Variable(element.GetAttribute("readOnly", "false"));
  551. Boolean copyBlob = new Variable(element.GetAttribute("copyBlob", "true"));
  552. String drivePath = null;
  553. LogLevel.Information.Trace(Name, "CloudDrive:\n\t{0:30}: {1}\n\t{2:30}: {3}\n\t{4:30}: {5}\n\t{6:30}: {7}\n", "PathKey", pathKey, "CloudDriveUri", pageBlobUri, "CreateIfNotExists", createIfNotExists, "ConnectionString", connectionString);
  554. ServiceManager.InitializeCloudDriveCache(); //i| Initialize Cache (once for all drives)
  555. try
  556. {
  557. //i|
  558. //i| Get cloud storage account.
  559. //i|
  560. CloudStorageAccount account;
  561. if ( !ServiceManager.IsRunningInCloud )
  562. {
  563. LogLevel.Information.Trace(Name, "CloudDrive running in Development Fabric (using local storage connection for clouddrive).");
  564. account = CloudStorageAccount.DevelopmentStorageAccount;
  565. //i| The first time running in the local dev fabric; create an empty drive. The new drive will show up as a drive letter,
  566. //i| manually copy the application files to the new drive. In all subsequent tests using the engine you're able to then
  567. //i| simulate the cloud based startup and teardown locally.
  568. createIfNotExists = true;
  569. }
  570. else
  571. {
  572. String cs = ( connectionString.Contains("DefaultEndpointsProtocol=") ) //i| A variable could be a connection string or a setting.
  573. ? connectionString
  574. : RoleEnvironment.GetConfigurationSettingValue(connectionString);
  575. cs = cs.Replace("https", "http"); //i| Cloud drives cannot use https; change if neccessary.
  576. account = CloudStorageAccount.Parse(cs);
  577. }
  578. //LogLevel.Information.TraceContent(Name, account.ToTraceString() ?? String.Empty, "CloudDrive : Account : ");
  579. //if (!account.CreateCloudBlobClient().GetBlobReference(pageBlobUri).Exists() && pageBlobUri.EndsWith("DrivePageBlobUri"))
  580. // pageBlobUri = DefaultSettings.CloudDriveUri;
  581. //i|
  582. //i| Check if already mounted.
  583. //i|
  584. CloudBlobClient client = account.CreateCloudBlobClient();
  585. CloudBlob blob = client.GetBlobReference(pageBlobUri);
  586. Boolean blobExists = blob.Exists();
  587. LogLevel.Information.Trace(Name, "CloudDrive configuration:\n\t{0:30}: {1}\n\t{2:30}: {3}\n\t{4:30}: {5}\n\t{6:30}: {7}\n", "Uri", blob.Uri.ToString(), "ContainerName", blob.Container.Name, "ContainerExists", blob.Container.Exists(), "BlobExists", blobExists);
  588. if (!blobExists)
  589. {
  590. LogLevel.Warning.Trace(Name, "CloudDrive pageblob not found.\n");
  591. if (createIfNotExists)
  592. {
  593. blob.Container.CreateIfNotExist();
  594. }
  595. else if (!pageBlobUri.EndsWith(".vhd"))
  596. {
  597. blob = client.GetBlobReference(DefaultSettings.CloudDriveUri);
  598. blobExists = blob.Exists();
  599. if (blobExists)
  600. {
  601. LogLevel.Information.Trace(Name, "CloudDrive using accelerator default location: '{0}'", DefaultSettings.CloudDriveUri);
  602. }
  603. else
  604. {
  605. throw new Exception("CloudDrive blob not found! Verify configuration settings.");
  606. }
  607. }
  608. }
  609. String driveKey = blob.Uri.ToString();
  610. if (ServiceManager.DrivePaths.ContainsKey(driveKey))
  611. {
  612. LogLevel.Information.Trace(Name, "CloudDrive already mounted and available at '{0}'.", ServiceManager.CloudDrives[driveKey].LocalPath);
  613. }
  614. else
  615. {
  616. //i|
  617. //i| New drive to mount.
  618. //i|
  619. CloudDrive drive = account.CreateCloudDrive(pageBlobUri);
  620. //i|
  621. //i| If specified, create a new empty cloud drive.
  622. //i|
  623. if (createIfNotExists)
  624. {
  625. blob.Container.CreateIfNotExist();
  626. drive.Protect(d => d.Create(cacheSizeInMB)); //i| Exceptions may be throw on success or pre-existing. Always attempt to mount anyway, so swallow this.
  627. }
  628. //i|
  629. //i| Mount cloud drive.
  630. //i|
  631. const Int32 totalAttempts = 3;
  632. for (Int32 attempt = 1; attempt <= totalAttempts; attempt++) //i| Per known azure issue multiple retries may be required to force a drive to mount.
  633. {
  634. try
  635. { drive.Mount(cacheSizeInMB, DriveMountOptions.None); //ServiceManager.IsRunningInCloud ? DriveMountOptions.Force : DriveMountOptions.None);
  636. }
  637. catch (CloudDriveException ex)
  638. {
  639. if (attempt == totalAttempts)
  640. {
  641. drive = account.GetCloneDrive(drive, cacheSizeInMB);
  642. //if (copyBlob)
  643. //{
  644. // try
  645. // {
  646. // LogLevel.Error.TraceException(Name, ex, "CloudDrive failed to mount.\n{0}\n", drive.ToTraceString());
  647. // LogLevel.Information.Trace(Name, "Creating clouddrive snapshot.");
  648. // Uri snapshot = drive.Snapshot();
  649. // Uri instanceUri = new Uri(pageBlobUri + RoleEnvironment.CurrentRoleInstance.Id);
  650. // LogLevel.Information.Trace(Name, "Creating read-only drive from snapshot: '{0}'; Instance: '{1}'", snapshot.AbsoluteUri, instanceUri.AbsoluteUri);
  651. // CloudDrive snapshotDrive = account.CreateCloudDrive(snapshot.AbsoluteUri);
  652. // LogLevel.Information.Trace(Name, "Coping snapshot drive to instance blob. Snapshot: '{0}'; Instance: '{1}'", snapshot.AbsoluteUri, instanceUri.AbsoluteUri);
  653. // snapshotDrive.CopyTo(new Uri(pageBlobUri + RoleEnvironment.CurrentRoleInstance.Id));
  654. // snapshotDrive.Unmount();
  655. // drive = account.CreateCloudDrive(instanceUri.ToString());
  656. // drive.Mount(cacheSize, DriveMountOptions.None);
  657. // drive.Protect(d => d.Create(cacheSize)); //i| Exceptions may be throw on success or pre-existing. Always attempt to mount anyway, so swallow this.
  658. // break;
  659. // }
  660. // catch (CloudDriveException cdex)
  661. // {
  662. // LogLevel.Error.TraceException(Name, ex, "Unable to mount snapshot drive.");
  663. // break;
  664. // }
  665. //}
  666. //LogLevel.Error.TraceException(Name, ex, "Unable to mount drive {0} of {1} attempts.", attempt, totalAttempts);
  667. //break;
  668. }
  669. else
  670. {
  671. LogLevel.Error.TraceException(Name, ex, "CloudDrive failed to mount.");
  672. }
  673. }
  674. if (!String.IsNullOrEmpty(drive.LocalPath)) //i| Check drive path to determine success. (Possible to throw an exception; yet still have a valid mounted drive.)
  675. break;
  676. }
  677. LogLevel.Information.Trace(Name, "CloudDrive mounted and available at '{0}'.", drive);
  678. //i|
  679. //i| Add to global collection of mounted drives; keying by the full absolute blob URI.
  680. //i|)
  681. if (!String.IsNullOrEmpty(drive.LocalPath))
  682. {
  683. drivePath = drive.LocalPath.TrimEnd('\\');
  684. ServiceManager.CloudDrives.Add(driveKey, drive);
  685. LogLevel.Information.Trace(Name, "CloudDrive mounted to drive: [{0}]", drivePath);
  686. }
  687. else
  688. {
  689. drivePath = ServiceManager.LocalStoragePath;
  690. LogLevel.Warning.Trace(Name, "Unable to mount drive; using local storage: [{0}]", drivePath);
  691. }
  692. ServiceManager.DrivePaths.Add(driveKey, drivePath);
  693. }
  694. //i|
  695. //i| Add path variable. (Get drive from global collection since drive may be newly mounted or previously existing.)
  696. //i|
  697. ServiceManager.Variables[pathKey] = ServiceManager.DrivePaths[driveKey];
  698. LogLevel.Information.Trace(Name, "CloudDrive available at: [{0}]", ServiceManager.Variables[pathKey]);
  699. }
  700. catch (Exception ex)
  701. {
  702. ServiceManager.ServiceState = ServiceState.MissingDependency;
  703. LogLevel.Error.TraceException(Name, ex, "An unknown failure occured mounting drive; see the exception details for additional information.");
  704. }
  705. }
  706. /// <summary>
  707. /// Processes the blob storage sync element.
  708. /// </summary>
  709. /// <param name="element">The element.</param>
  710. private void ProcessBlobSyncElement(XElement element)
  711. {
  712. Boolean ignoreAdditionalFiles = new Variable(element.GetAttribute("ignoreAdditionalFiles") ?? "true");
  713. SyncProviderType providerType = ((String)new Variable(element.GetAttribute("providerType"))).ToEnum<SyncProviderType>() ?? SyncProviderType.CloudSync;
  714. SyncManager syncManager = new SyncManager(providerType)
  715. {
  716. ContainerName = (String) new Variable(element.GetAttribute("blobDirectoryUri")),
  717. LocalPath = (String) new Variable(element.GetAttribute("localDirectoryPath")),
  718. Interval = TimeSpan.FromSeconds(new Variable(element.GetAttribute("intervalInSeconds", "0"))),
  719. SyncDirection = ((String)new Variable(element.GetAttribute("direction"))).ToEnum<SyncDirectionOrder>() ?? SyncDirectionOrder.Download,
  720. Account = CloudStorageAccount.Parse(new Variable(element.GetAttribute("connectionString")))
  721. };
  722. if ( syncManager.Interval != TimeSpan.Zero ) //i| Spawns a thread to sync on timed interval.
  723. {
  724. syncManager.Start();
  725. BlobSyncInstances.Add(syncManager);
  726. }
  727. else //i| Sync once then continue.
  728. {
  729. syncManager.Sync();
  730. }
  731. }
  732. /// <summary>
  733. /// Processes a file copy between local locations. (eg. From approot read-only upload location to local drive storage.)
  734. /// </summary>
  735. /// <param name="element">The element.</param>
  736. private void ProcessLocalCopyElement(XElement element)
  737. {
  738. String source = new Variable(element.GetAttribute("source"));
  739. String destination = new Variable(element.GetAttribute("destination"));
  740. LogLevel.Information.Trace(Name, "FileCopy : Starting : {{{{ Source: '{0}' }}, {{ Destination, '{1}' }}}}.", source, destination);
  741. Int32 filesCopied = CopyDirectory(source, destination, 0);
  742. LogLevel.Information.Trace(Name, "FileCopy : Finished : '{0}' files copied.", filesCopied);
  743. }
  744. /// <summary>
  745. /// Processes the file validation element.
  746. /// </summary>
  747. /// <param name="element">The element.</param>
  748. private Boolean ProcessFileValidationElement(XElement element)
  749. {
  750. String path = new Variable(element.GetAttribute("path"));
  751. Boolean checkAccess = new Variable(element.GetAttribute("checkAccess") ?? "false");
  752. Boolean required = new Variable(element.GetAttribute("required") ?? "false");
  753. if ( File.Exists(path) )
  754. {
  755. if ( !checkAccess )
  756. {
  757. LogLevel.Information.Trace(Name, "FileCheck : Verified : {{ '{0}' }}.", path);
  758. }
  759. else
  760. {
  761. var ac = File.GetAccessControl(path);
  762. var at = File.GetAttributes(path);
  763. LogLevel.Information.TraceContent(Name,
  764. String.Format(
  765. "\r\n\t[(\"ACCESS CONTROL\" )]\r\n{0}\r\n\t[(\"ATTRIBUTES\" )]\r\n{1}",
  766. ac.ToString("\t[ {0} : {1} ]\r\n", true, true),
  767. at.ToString("\t[ {0} : {1} ]\r\n", true)
  768. ), String.Format("FileCheck : Validated : {{ '{0}' }}.", path));
  769. }
  770. return true;
  771. }
  772. else if (required)
  773. {
  774. ServiceManager.ServiceState = ServiceState.MissingDependency;
  775. LogLevel.Error.Trace(Name, "FileCheck : File Not Found : {{ '{0}' }}.", path);
  776. }
  777. else

Large files files are truncated, but you can click here to view the full file