PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Servicing/1.0/Release/Product/Python/Hpc/HpcSupport/HpcLauncher.cs

#
C# | 986 lines | 847 code | 93 blank | 46 comment | 83 complexity | acc735ff581fa7d42b2fbaf5bb5e0f08 MD5 | raw file
Possible License(s): Apache-2.0, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. * ***************************************************************************/
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Diagnostics;
  17. using System.IO;
  18. using System.Net;
  19. using System.Net.Security;
  20. using System.Net.Sockets;
  21. using System.Runtime.InteropServices;
  22. using System.Security.Principal;
  23. using System.Threading;
  24. using System.Windows.Forms;
  25. using Microsoft.Hpc.Scheduler;
  26. using Microsoft.Hpc.Scheduler.Properties;
  27. using Microsoft.PythonTools.Project;
  28. using Microsoft.VisualStudio;
  29. using Microsoft.VisualStudio.Debugger.Interop;
  30. using Microsoft.VisualStudio.Shell;
  31. using Microsoft.VisualStudio.Shell.Interop;
  32. using Microsoft.Win32;
  33. namespace Microsoft.PythonTools.Hpc {
  34. class HpcLauncher : IProjectLauncher {
  35. private readonly IPythonProject _project;
  36. private static readonly Guid _authGuid = Guid.NewGuid();
  37. private static Thread _listenerThread;
  38. private static Socket _listenerSocket;
  39. private static readonly AutoResetEvent _listenerThreadStarted = new AutoResetEvent(false);
  40. private const string MpiShimExe = "Microsoft.PythonTools.MpiShim.exe";
  41. private static bool _createdGeneralPane;
  42. private static HiddenForm _pumpForm;
  43. #region Debugger constants for Python debugger - cloned from AD7Engine.cs
  44. private const string DebugEngineId = "{EC1375B7-E2CE-43E8-BF75-DC638DE1F1F9}";
  45. private static Guid DebugEngineGuid = new Guid(DebugEngineId);
  46. /// <summary>
  47. /// Specifies whether the process should prompt for input before exiting on an abnormal exit.
  48. /// </summary>
  49. private const string WaitOnAbnormalExitSetting = "WAIT_ON_ABNORMAL_EXIT";
  50. /// <summary>
  51. /// Specifies whether the process should prompt for input before exiting on a normal exit.
  52. /// </summary>
  53. private const string WaitOnNormalExitSetting = "WAIT_ON_NORMAL_EXIT";
  54. /// <summary>
  55. /// Specifies if the output should be redirected to the visual studio output window.
  56. /// </summary>
  57. private const string RedirectOutputSetting = "REDIRECT_OUTPUT";
  58. /// <summary>
  59. /// Specifies options which should be passed to the Python interpreter before the script. If
  60. /// the interpreter options should include a semicolon then it should be escaped as a double
  61. /// semi-colon.
  62. /// </summary>
  63. private const string InterpreterOptions = "INTERPRETER_OPTIONS";
  64. /// <summary>
  65. /// Specifies a directory mapping in the form of:
  66. ///
  67. /// OldDir|NewDir
  68. ///
  69. /// for mapping between the files on the local machine and the files deployed on the
  70. /// running machine.
  71. /// </summary>
  72. private const string DirMappingSetting = "DIR_MAPPING";
  73. #endregion
  74. public HpcLauncher(IPythonProject project) {
  75. _project = project;
  76. }
  77. #region IPythonLauncher Members
  78. public int LaunchProject(bool debug) {
  79. string filename = _project.GetProperty(CommonConstants.StartupFile);
  80. return LaunchFile(filename, debug);
  81. }
  82. private static void EnsureListenerThread() {
  83. lock (_listenerThreadStarted) {
  84. if (_listenerThread == null || !_listenerThread.IsAlive) {
  85. _listenerThreadStarted.Reset();
  86. _listenerThread = new Thread(SocketThread);
  87. _listenerThread.SetApartmentState(ApartmentState.MTA);
  88. _listenerThread.Name = "Hpc Debugging Thread";
  89. _listenerThread.Start();
  90. _listenerThreadStarted.WaitOne();
  91. }
  92. }
  93. }
  94. private static void SocketThread() {
  95. try {
  96. _listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
  97. _listenerSocket.Bind(new IPEndPoint(IPAddress.Any, 0));
  98. _listenerSocket.Listen(512);
  99. } finally {
  100. _listenerThreadStarted.Set();
  101. }
  102. try {
  103. for (; ; ) {
  104. Socket request;
  105. try {
  106. request = _listenerSocket.Accept();
  107. } catch (IOException) {
  108. lock (_listenerThreadStarted) {
  109. _listenerThread = null;
  110. _listenerSocket = null;
  111. }
  112. return;
  113. }
  114. ThreadPool.QueueUserWorkItem(ProcessDebugRequest, request);
  115. }
  116. } catch (Exception e) {
  117. Debug.Fail("Unexpected exception" + e);
  118. }
  119. }
  120. private static void ProcessDebugRequest(object socket) {
  121. Socket request = (Socket)socket;
  122. try {
  123. var secureStream = new NegotiateStream(new NetworkStream(request, false), true);
  124. secureStream.AuthenticateAsServer();
  125. if (!secureStream.IsAuthenticated || !secureStream.IsEncrypted) {
  126. request.Close();
  127. return;
  128. }
  129. WindowsIdentity winIdentity = secureStream.RemoteIdentity as WindowsIdentity;
  130. if (winIdentity == null ||
  131. winIdentity.User != System.Security.Principal.WindowsIdentity.GetCurrent().User) {
  132. request.Close();
  133. return;
  134. }
  135. var reader = new StreamReader(secureStream);
  136. string auth = reader.ReadLine();
  137. Guid g;
  138. if (!Guid.TryParse(auth, out g) || g != _authGuid) {
  139. request.Close();
  140. return;
  141. }
  142. string exe = reader.ReadLine();
  143. string curDir = reader.ReadLine();
  144. string projectDir = reader.ReadLine();
  145. string args = reader.ReadLine();
  146. string machineName = reader.ReadLine();
  147. string options = reader.ReadLine();
  148. uint pid = 0;
  149. string errorText = "";
  150. var res = _pumpForm.BeginInvoke((Action)(() => {
  151. pid = LaunchDebugger(exe, curDir, projectDir, args, machineName, options, out errorText);
  152. }));
  153. res.AsyncWaitHandle.WaitOne();
  154. var writer = new StreamWriter(secureStream);
  155. writer.WriteLine(pid.ToString());
  156. if (pid == 0) {
  157. writer.WriteLine(errorText.Length);
  158. writer.WriteLine(errorText);
  159. }
  160. writer.Flush();
  161. } catch (IOException) {
  162. } catch (Exception) {
  163. Debug.Assert(false);
  164. }
  165. }
  166. private static void EnsureHiddenForm() {
  167. if (_pumpForm == null) {
  168. _pumpForm = new HiddenForm();
  169. _pumpForm.Show();
  170. }
  171. }
  172. class HiddenForm : Form {
  173. protected override void OnShown(EventArgs e) {
  174. this.Hide();
  175. }
  176. const int SW_HIDE = 0;
  177. const int HWND_MESSAGE = -3;
  178. [DllImport("user32.dll")]
  179. static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
  180. [DllImport("user32.dll")]
  181. public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
  182. protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); SetParent(this.Handle, new IntPtr(HWND_MESSAGE)); }
  183. protected override void SetVisibleCore(bool value) {
  184. ShowWindow(Handle, SW_HIDE);
  185. }
  186. }
  187. private static uint LaunchDebugger(string exe, string curDir, string projectDir, string args, string machineName, string options, out string error) {
  188. var debugger = (IVsDebugger2)HpcSupportPackage.GetGlobalService(typeof(SVsShellDebugger));
  189. VsDebugTargetInfo2 debugInfo = new VsDebugTargetInfo2();
  190. debugInfo.cbSize = (uint)Marshal.SizeOf(typeof(VsDebugTargetInfo2));
  191. debugInfo.dlo = (uint)DEBUG_LAUNCH_OPERATION.DLO_Custom;
  192. debugInfo.guidLaunchDebugEngine = DebugEngineGuid;
  193. debugInfo.dwDebugEngineCount = 1;
  194. debugInfo.guidPortSupplier = new Guid("{708C1ECA-FF48-11D2-904F-00C04FA302A1}"); // local port supplier
  195. debugInfo.LaunchFlags = (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_WaitForAttachComplete | (uint)__VSDBGLAUNCHFLAGS5.DBGLAUNCH_BreakOneProcess;
  196. debugInfo.bstrRemoteMachine = machineName;
  197. debugInfo.bstrExe = exe;
  198. debugInfo.bstrCurDir = curDir;
  199. debugInfo.bstrArg = args;
  200. debugInfo.bstrOptions = DirMappingSetting + "=" + projectDir + "|" + curDir;
  201. if (!String.IsNullOrWhiteSpace(options)) {
  202. debugInfo.bstrOptions += ";" + options;
  203. }
  204. debugInfo.pDebugEngines = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Guid)));
  205. if (debugInfo.pDebugEngines == IntPtr.Zero) {
  206. error = "Out of memory";
  207. return 0;
  208. }
  209. try {
  210. Marshal.StructureToPtr(DebugEngineGuid, debugInfo.pDebugEngines, false);
  211. IntPtr memory = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(VsDebugTargetInfo2)));
  212. if (memory == IntPtr.Zero) {
  213. error = "Out of memory";
  214. return 0;
  215. }
  216. try {
  217. Marshal.StructureToPtr(debugInfo, memory, false);
  218. int hr = debugger.LaunchDebugTargets2(1, memory);
  219. if (ErrorHandler.Failed(hr)) {
  220. var uiShell = (IVsUIShell)HpcSupportPackage.GetGlobalService(typeof(SVsUIShell));
  221. string errorText;
  222. if (ErrorHandler.Succeeded(uiShell.GetErrorInfo(out errorText))) {
  223. error = errorText;
  224. } else {
  225. error = "Unknown error";
  226. }
  227. OutputState(String.Format("Launching debugger on server failed ({0:X}):\r\n\r\n{1}\r\n", hr, error));
  228. return 0;
  229. } else {
  230. var structure = (VsDebugTargetInfo2)Marshal.PtrToStructure(memory, typeof(VsDebugTargetInfo2));
  231. error = "";
  232. return structure.dwProcessId;
  233. }
  234. } finally {
  235. Marshal.FreeCoTaskMem(memory);
  236. }
  237. } finally {
  238. //Marshal.FreeCoTaskMem(debugInfo.pDebugEngines);
  239. }
  240. }
  241. public int LaunchFile(string filename, bool debug) {
  242. var clusterEnv = new ClusterEnvironment(_project.GetProperty(ClusterOptions.RunEnvironmentSetting));
  243. if (debug) {
  244. EnsureHiddenForm();
  245. EnsureListenerThread();
  246. }
  247. string workingDir, publishUrl;
  248. if (clusterEnv.HeadNode == "localhost") {
  249. workingDir = _project.GetProperty(ClusterOptions.WorkingDirSetting);
  250. if (String.IsNullOrWhiteSpace(workingDir)) {
  251. workingDir = Path.Combine(Path.GetTempPath(), "HpcPyDebug" + Guid.NewGuid().ToString());
  252. }
  253. if (!Directory.Exists(workingDir)) {
  254. Directory.CreateDirectory(workingDir);
  255. }
  256. publishUrl = "file://" + workingDir;
  257. } else {
  258. workingDir = GetWorkingDir(clusterEnv);
  259. // make sure we have a valid deployement dir as well
  260. string deploymentDir;
  261. if (!TryGetDeploymentDir(out deploymentDir)) {
  262. return VSConstants.S_OK;
  263. }
  264. publishUrl = deploymentDir;
  265. }
  266. string exe, arguments;
  267. if (!TryBuildCommandLine(debug, clusterEnv, filename, workingDir, out exe, out arguments) ||
  268. !TryPublishProject(clusterEnv, publishUrl)) {
  269. return VSConstants.S_OK;
  270. }
  271. if (clusterEnv.HeadNode == "localhost") {
  272. // run locally
  273. if (debug) {
  274. var startInfo = new ProcessStartInfo(exe, arguments);
  275. LaunchRedirectedToVsOutputWindow(startInfo, false);
  276. } else {
  277. Process.Start(exe, arguments);
  278. }
  279. } else {
  280. EnsureGeneralPane();
  281. var commandLine = exe + " " + arguments;
  282. var scheduler = new Scheduler();
  283. scheduler.Connect(clusterEnv.HeadNode);
  284. var job = CreateSchedulerJob(commandLine, clusterEnv, scheduler, debug);
  285. scheduler.AddJob(job);
  286. SetStatus("Scheduling job on server...");
  287. ScheduleJob(scheduler, job);
  288. }
  289. return VSConstants.S_OK;
  290. }
  291. private bool TryGetDeploymentDir(out string deploymentDir) {
  292. deploymentDir = _project.GetProperty(ClusterOptions.DeploymentDirSetting);
  293. if (String.IsNullOrWhiteSpace(deploymentDir)) {
  294. deploymentDir = _project.GetProperty("PublishUrl");
  295. if (!String.IsNullOrWhiteSpace(deploymentDir)) {
  296. if (new Uri(deploymentDir).Scheme != "file") {
  297. MessageBox.Show(
  298. "Publishing scheme is not a file path, please configure the deployment directory for HPC debugging in project properties",
  299. "Deployment Directory Not Configured",
  300. MessageBoxButtons.OK,
  301. MessageBoxIcon.Error
  302. );
  303. return false;
  304. }
  305. } else {
  306. MessageBox.Show(
  307. "Publishing path is not set, please configure the publish path in project properties.",
  308. "Publishing Path Not Configured",
  309. MessageBoxButtons.OK,
  310. MessageBoxIcon.Error
  311. );
  312. return false;
  313. }
  314. }
  315. return true;
  316. }
  317. #endregion
  318. private ISchedulerJob CreateSchedulerJob(string commandLine, ClusterEnvironment clusterEnv, Scheduler scheduler, bool debug) {
  319. var job = scheduler.CreateJob();
  320. job.IsExclusive = true;
  321. job.Name = debug ? "Python MPI Debugging Session" : "Python MPI Session";
  322. string mpiExecCommand, deploymentDir;
  323. if (!TryGetMpiExecCommand(clusterEnv.HeadNode, out mpiExecCommand) ||
  324. !TryGetDeploymentDir(out deploymentDir)) {
  325. // unreachable, we've already built the command line (which gets the MPI exec command as well) and checked that we could get the working
  326. throw new InvalidOperationException();
  327. }
  328. string workingDir = GetWorkingDir(clusterEnv);
  329. ISchedulerTask[] tasks;
  330. var runTask = job.CreateTask();
  331. runTask.Name = debug ? "Debug MPI Task" : "Run MPI Task";
  332. if (!IsPathUnc(workingDir)) {
  333. // we need to copy the files locally onto the cluster
  334. var copyTask = job.CreateTask();
  335. copyTask.Name = "Copy files";
  336. copyTask.Type = TaskType.NodePrep;
  337. var cleanupTask = job.CreateTask();
  338. cleanupTask.Name = "Cleanup files";
  339. tasks = new[] { copyTask, runTask, cleanupTask };
  340. copyTask.CommandLine = String.Format("\"%WINDIR%\\System32\\cmd.exe\" /c mkdir \"{1}\" & robocopy /s \"{0}\" \"{1}\" & if not errorlevel 2 exit 0", deploymentDir, workingDir);
  341. cleanupTask.CommandLine = String.Format("\"%WINDIR%\\System32\\cmd.exe\" /c rmdir /s /q \"{0}\"", workingDir);
  342. cleanupTask.Type = TaskType.NodeRelease;
  343. } else {
  344. tasks = new[] { runTask };
  345. }
  346. runTask.CommandLine = commandLine;
  347. runTask.WorkDirectory = workingDir;
  348. job.NodeGroups.Add(clusterEnv.PickNodesFrom);
  349. SetJobParameters(clusterEnv, job, tasks);
  350. job.AddTasks(tasks);
  351. job.OnJobState += JobStateChanged;
  352. job.OnTaskState += OnTaskStateChange;
  353. return job;
  354. }
  355. private string GetWorkingDir(ClusterEnvironment clusterEnv) {
  356. string workingDir = _project.GetProperty(ClusterOptions.WorkingDirSetting);
  357. if (String.IsNullOrWhiteSpace(workingDir)) {
  358. if (clusterEnv.HeadNode == "localhost") {
  359. return _project.GetWorkingDirectory();
  360. }
  361. return "%TEMP%\\%USERNAME%\\" + _project.ProjectName;
  362. }
  363. return workingDir;
  364. }
  365. private static void OnTaskStateChange(object sender, ITaskStateEventArg args) {
  366. if (args.NewState == TaskState.Failed) {
  367. var scheduler = (Scheduler)sender;
  368. var job = scheduler.OpenJob(args.JobId);
  369. var task = job.OpenTask(args.TaskId);
  370. string output = task.Output;
  371. if (!String.IsNullOrWhiteSpace(output)) {
  372. var outWin = (IVsOutputWindow)CommonPackage.GetGlobalService(typeof(IVsOutputWindow));
  373. IVsOutputWindowPane pane;
  374. if (ErrorHandler.Succeeded(outWin.GetPane(VSConstants.GUID_OutWindowGeneralPane, out pane))) {
  375. pane.Activate();
  376. pane.OutputString(output);
  377. }
  378. }
  379. }
  380. }
  381. private static void SetJobParameters(ClusterEnvironment clusterEnv, ISchedulerJob job, ISchedulerTask[] tasks) {
  382. if (clusterEnv.SelectedNodes != null) {
  383. SelectClusterNodes(clusterEnv, job, tasks);
  384. } else {
  385. switch (clusterEnv.ScheduleProcessPer) {
  386. case ScheduleProcessPer.Core:
  387. job.MinimumNumberOfCores = job.MaximumNumberOfCores = clusterEnv.NumberOfProcesses;
  388. foreach (var task in tasks) {
  389. if (ShouldSetTaskParameters(task)) {
  390. task.MinimumNumberOfCores = task.MaximumNumberOfCores = clusterEnv.NumberOfProcesses;
  391. }
  392. }
  393. break;
  394. case ScheduleProcessPer.Node:
  395. job.MinimumNumberOfNodes = job.MaximumNumberOfNodes = clusterEnv.NumberOfProcesses;
  396. foreach (var task in tasks) {
  397. if (ShouldSetTaskParameters(task)) {
  398. task.MinimumNumberOfNodes = task.MaximumNumberOfNodes = clusterEnv.NumberOfProcesses;
  399. }
  400. }
  401. break;
  402. case ScheduleProcessPer.Socket:
  403. job.MinimumNumberOfSockets = job.MaximumNumberOfSockets = clusterEnv.NumberOfProcesses;
  404. foreach (var task in tasks) {
  405. if (ShouldSetTaskParameters(task)) {
  406. task.MinimumNumberOfSockets = task.MaximumNumberOfSockets = clusterEnv.NumberOfProcesses;
  407. }
  408. }
  409. break;
  410. }
  411. }
  412. }
  413. private static bool ShouldSetTaskParameters(ISchedulerTask task) {
  414. return task.Type != TaskType.NodePrep && task.Type != TaskType.NodeRelease;
  415. }
  416. private static readonly string[] _pyDebuggerFiles = new string[] {
  417. "visualstudio_py_debugger.py",
  418. "visualstudio_py_launcher.py",
  419. "PyDebugAttach.dll",
  420. "X64\\PyDebugAttach.dll",
  421. "Microsoft.PythonTools.Debugger.dll",
  422. "Microsoft.PythonTools.Analysis.dll",
  423. "Microsoft.PythonTools.Attacher.exe",
  424. "Microsoft.PythonTools.AttacherX86.exe"
  425. };
  426. private static readonly string[] _hpcDebuggerFiles = new string[] {
  427. MpiShimExe
  428. };
  429. private static readonly string[] _vsAssemblies = new string[] {
  430. typeof(IDebugBinderDirect100).Assembly.Location,
  431. typeof(IDebugAddress).Assembly.Location,
  432. typeof(VsColors).Assembly.Location,
  433. typeof(Microsoft.VisualStudio.OLE.Interop.FILETIME).Assembly.Location // MODULE_INFO structure refers to a type in this...
  434. };
  435. /// <summary>
  436. /// Publishes the project if the user has configured the publish on run.
  437. /// </summary>
  438. private bool TryPublishProject(ClusterEnvironment environment, string publishOverrideUrl) {
  439. if (_project.PublishBeforeRun() || environment.HeadNode == "localhost") {
  440. string msg = null;
  441. try {
  442. var vsInstallDir = (string)HpcSupportPackage.Instance.ApplicationRegistryRoot.GetValue("InstallDir");
  443. List<IPublishFile> allFiles = new List<IPublishFile>();
  444. // add python debugger files
  445. string pyInstallDir = GetPythonToolsInstallPath();
  446. foreach (var file in _pyDebuggerFiles) {
  447. allFiles.Add(new CopyFile(Path.Combine(pyInstallDir, file), file));
  448. }
  449. string pyHpcInstallDir = GetPythonHpcToolsInstallPath();
  450. foreach (var file in _hpcDebuggerFiles) {
  451. allFiles.Add(new CopyFile(Path.Combine(pyHpcInstallDir, file), file));
  452. }
  453. // add VS components that we need to run
  454. foreach (var file in _vsAssemblies) {
  455. allFiles.Add(new CopyFile(file, Path.GetFileName(file)));
  456. }
  457. // Add vs remote debugger components.
  458. string basePath = Path.Combine(Path.Combine(vsInstallDir, "Remote Debugger"), _project.TargetPlatform().ToString()) + "\\";
  459. foreach (var file in Directory.GetFiles(basePath)) {
  460. allFiles.Add(new CopyFile(file, file.Substring(basePath.Length)));
  461. }
  462. if (!_project.Publish(new PublishProjectOptions(allFiles.ToArray(), publishOverrideUrl))) {
  463. msg = "Publishing not configured or unknown publishing schema";
  464. }
  465. } catch (PublishFailedException e) {
  466. msg = e.InnerException.Message;
  467. }
  468. if (msg != null && CancelLaunch(msg)) {
  469. return false;
  470. }
  471. }
  472. return true;
  473. }
  474. class CopyFile : IPublishFile {
  475. private readonly string _source, _dest;
  476. public CopyFile(string source, string dest) {
  477. _source = source;
  478. _dest = dest;
  479. }
  480. #region IPublishFile Members
  481. public string SourceFile {
  482. get { return _source; }
  483. }
  484. public string DestinationFile {
  485. get { return _dest; }
  486. }
  487. #endregion
  488. }
  489. /// <summary>
  490. /// Builds the command line that each task should execute.
  491. /// </summary>
  492. private bool TryBuildCommandLine(bool debug, ClusterEnvironment environment, string startupFile, string workingDir, out string exe, out string arguments) {
  493. exe = arguments = null;
  494. string schedulerNode = environment.HeadNode;
  495. var appCommand = _project.GetProperty(ClusterOptions.AppCommandSetting);
  496. if (String.IsNullOrWhiteSpace(appCommand)) {
  497. MessageBox.Show("The path to the interpreter on the cluster is not configured. Please update project properties->Debug->Python Interpreter to point at the correct location of the interpreter", "Python Tools for Visual Studio");
  498. return false;
  499. }
  500. string mpiExecCommand;
  501. if (!TryGetMpiExecCommand(schedulerNode, out mpiExecCommand)) {
  502. return false;
  503. }
  504. var appArgs = _project.GetProperty(ClusterOptions.AppArgumentsSetting);
  505. if (!String.IsNullOrWhiteSpace(workingDir)) {
  506. startupFile = Path.Combine(workingDir, startupFile);
  507. }
  508. exe = "\"" + mpiExecCommand + "\"";
  509. arguments = "";
  510. if (schedulerNode == "localhost") {
  511. arguments = "-n " + environment.NumberOfProcesses + " ";
  512. }
  513. string tmpArgs = "\"" + appCommand + "\" \"" + startupFile + "\" \"" + appArgs + "\"";
  514. if (debug) {
  515. arguments += "\"" + Path.Combine(workingDir, MpiShimExe) + "\" " +
  516. ((IPEndPoint)_listenerSocket.LocalEndPoint).Port + " " +
  517. _authGuid.ToString() + " " +
  518. GetLocalIPv4Address(schedulerNode) + " " +
  519. "\"" + FixDir(workingDir) + "\" " +
  520. "\"" + FixDir(_project.ProjectDirectory) + "\" " +
  521. "\"" + GetDebugOptions(environment) + "\" " +
  522. tmpArgs;
  523. } else {
  524. arguments += tmpArgs;
  525. }
  526. return true;
  527. }
  528. private string GetDebugOptions(ClusterEnvironment clusterEnv) {
  529. string options = "";
  530. if (PythonToolsPackage.Instance.OptionsPage.TeeStandardOutput) {
  531. options = RedirectOutputSetting + "=True";
  532. }
  533. if (clusterEnv.HeadNode == "localhost") { // don't wait on the cluster, there's no one to press enter.
  534. if (PythonToolsPackage.Instance.OptionsPage.WaitOnAbnormalExit) {
  535. if (!String.IsNullOrEmpty(options)) {
  536. options += ";";
  537. }
  538. options += WaitOnAbnormalExitSetting + "=True";
  539. }
  540. if (PythonToolsPackage.Instance.OptionsPage.WaitOnNormalExit) {
  541. if (!String.IsNullOrEmpty(options)) {
  542. options += ";";
  543. }
  544. options += WaitOnNormalExitSetting + "=True";
  545. }
  546. }
  547. var interpArgs = _project.GetProperty(CommonConstants.InterpreterArguments);
  548. if (!String.IsNullOrWhiteSpace(interpArgs)) {
  549. if (!String.IsNullOrEmpty(options)) {
  550. options += ";";
  551. }
  552. options += InterpreterOptions + "=" + interpArgs.Replace(";", ";;");
  553. }
  554. return options;
  555. }
  556. private bool TryGetMpiExecCommand(string schedulerNode, out string mpiExecCommand) {
  557. mpiExecCommand = _project.GetProperty(ClusterOptions.MpiExecPathSetting);
  558. if (String.IsNullOrWhiteSpace(mpiExecCommand)) {
  559. if (schedulerNode == "localhost") {
  560. string ccpHome = Environment.GetEnvironmentVariable("CCP_HOME");
  561. string mpiexecPath;
  562. if (String.IsNullOrWhiteSpace(ccpHome) || !File.Exists(mpiexecPath = Path.Combine(ccpHome, "Bin\\mpiexec.exe"))) {
  563. string sdkHome = Environment.GetEnvironmentVariable("CCP_SDK");
  564. if (String.IsNullOrWhiteSpace(sdkHome) || !File.Exists(mpiexecPath = Path.Combine(sdkHome, "Bin\\mpiexec.exe"))) {
  565. MessageBox.Show("Could not find mpiexec.exe. Please specify path in project settings->Debug or install Microsoft HPC Pack or Microsoft HPC Pack SDK", "Python Tools for Visual Studio");
  566. return false;
  567. }
  568. }
  569. mpiExecCommand = mpiexecPath;
  570. } else {
  571. mpiExecCommand = "%CCP_HOME%\\Bin\\mpiexec.exe";
  572. }
  573. }
  574. return true;
  575. }
  576. /// <summary>
  577. /// make sure a directory doesn't end in a backslash which messes up our escaping.
  578. /// </summary>
  579. private string FixDir(string dir) {
  580. if (dir.EndsWith("\\")) {
  581. return dir.Substring(0, dir.Length - 1);
  582. }
  583. return dir;
  584. }
  585. private string GetLocalIPv4Address(string schedulerName) {
  586. IPEndPoint ep = null;
  587. try {
  588. using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) {
  589. socket.Bind(new IPEndPoint(IPAddress.Any, 0));
  590. IPAddress[] addresses = Dns.GetHostAddresses(schedulerName);
  591. foreach (IPAddress ip in addresses) {
  592. try {
  593. IPEndPoint remoteEP = new IPEndPoint(ip, 0);
  594. ep = QueryRoutingInterface(socket, remoteEP);
  595. if (ep.AddressFamily == AddressFamily.InterNetwork) {
  596. break;
  597. }
  598. } catch (SocketException) {
  599. // ignore it, and try another address
  600. }
  601. }
  602. }
  603. } catch (SocketException) {
  604. // if can't get EP, we will return machine name
  605. }
  606. if (ep != null) {
  607. return ep.Address.ToString();
  608. } else {
  609. return Environment.MachineName;
  610. }
  611. }
  612. private static IPEndPoint QueryRoutingInterface(Socket socket, IPEndPoint remoteEndPoint) {
  613. SocketAddress address = remoteEndPoint.Serialize();
  614. byte[] remoteAddrBytes = new byte[address.Size];
  615. for (int i = 0; i < address.Size; i++) {
  616. remoteAddrBytes[i] = address[i];
  617. }
  618. byte[] outBytes = new byte[remoteAddrBytes.Length];
  619. socket.IOControl(IOControlCode.RoutingInterfaceQuery, remoteAddrBytes, outBytes);
  620. for (int i = 0; i < address.Size; i++) {
  621. address[i] = outBytes[i];
  622. }
  623. EndPoint ep = remoteEndPoint.Create(address);
  624. return (IPEndPoint)ep;
  625. }
  626. private static void EnsureGeneralPane() {
  627. if (!_createdGeneralPane) {
  628. var outWin = (IVsOutputWindow)CommonPackage.GetGlobalService(typeof(IVsOutputWindow));
  629. var guid = VSConstants.GUID_OutWindowGeneralPane;
  630. outWin.CreatePane(ref guid, "General", 1, 0);
  631. _createdGeneralPane = true;
  632. }
  633. }
  634. private static void ScheduleJob(Scheduler scheduler, ISchedulerJob job) {
  635. var outWin = (IVsOutputWindow)CommonPackage.GetGlobalService(typeof(IVsOutputWindow));
  636. IVsOutputWindowPane pane;
  637. if (ErrorHandler.Succeeded(outWin.GetPane(VSConstants.GUID_OutWindowGeneralPane, out pane))) {
  638. pane.Activate();
  639. pane.OutputString("Submitting job " + job.Id + Environment.NewLine);
  640. }
  641. var shell = (IVsUIShell)HpcSupportPackage.GetGlobalService(typeof(SVsUIShell));
  642. IntPtr owner;
  643. if (ErrorHandler.Succeeded(shell.GetDialogOwnerHwnd(out owner))) {
  644. scheduler.SetInterfaceMode(false, owner);
  645. }
  646. ThreadPool.QueueUserWorkItem(x => {
  647. try {
  648. scheduler.SubmitJob(job, null, null);
  649. } catch (Exception ex) {
  650. string msg;
  651. msg = "Failed to submit job " + ex.ToString();
  652. if (pane != null) {
  653. pane.OutputString(msg);
  654. } else {
  655. MessageBox.Show(msg, "Python Tools for Visual Studio");
  656. }
  657. }
  658. });
  659. }
  660. private static Process LaunchRedirectedToVsOutputWindow(ProcessStartInfo info, bool reportExit = true) {
  661. info.CreateNoWindow = true;
  662. info.RedirectStandardError = true;
  663. info.RedirectStandardInput = true;
  664. info.RedirectStandardOutput = true;
  665. info.UseShellExecute = false;
  666. var process = Process.Start(info);
  667. new Thread(new Redirector(process, process.StandardOutput, reportExit).RedirectOutput).Start();
  668. new Thread(new Redirector(process, process.StandardError).RedirectOutput).Start();
  669. return process;
  670. }
  671. class Redirector {
  672. private readonly Process _process;
  673. private readonly StreamReader _reader;
  674. private readonly bool _reportExit;
  675. public Redirector(Process process, StreamReader reader, bool reportExit = false) {
  676. _process = process;
  677. _reader = reader;
  678. _reportExit = reportExit;
  679. }
  680. public void RedirectOutput() {
  681. var outWin = (IVsOutputWindow)CommonPackage.GetGlobalService(typeof(IVsOutputWindow));
  682. IVsOutputWindowPane pane;
  683. char[] buffer = new char[1024];
  684. if (ErrorHandler.Succeeded(outWin.GetPane(VSConstants.GUID_OutWindowDebugPane, out pane))) {
  685. pane.Activate();
  686. while (!_process.HasExited) {
  687. int bytesRead = _reader.Read(buffer, 0, buffer.Length);
  688. pane.OutputString(new string(buffer, 0, bytesRead));
  689. }
  690. if (_reportExit) {
  691. if (_process.ExitCode != 0) {
  692. SetStatus("Submitting job failed.");
  693. }
  694. }
  695. }
  696. }
  697. }
  698. private static void SelectClusterNodes(ClusterEnvironment clusterEnv, ISchedulerJob job, ISchedulerTask[] tasks) {
  699. foreach (var node in clusterEnv.SelectedNodes) {
  700. foreach (var task in tasks) {
  701. if (ShouldSetTaskParameters(task)) {
  702. task.RequiredNodes.Add(node);
  703. }
  704. }
  705. }
  706. switch (clusterEnv.ScheduleProcessPer) {
  707. case ScheduleProcessPer.Socket:
  708. job.MinimumNumberOfSockets = job.MaximumNumberOfSockets = clusterEnv.NumberOfProcesses;
  709. foreach (var task in tasks) {
  710. if (ShouldSetTaskParameters(task)) {
  711. task.MinimumNumberOfSockets = task.MaximumNumberOfSockets = clusterEnv.NumberOfProcesses;
  712. }
  713. }
  714. break;
  715. case ScheduleProcessPer.Node:
  716. job.MinimumNumberOfNodes = job.MaximumNumberOfNodes = clusterEnv.NumberOfProcesses;
  717. foreach (var task in tasks) {
  718. if (ShouldSetTaskParameters(task)) {
  719. task.MinimumNumberOfNodes = task.MaximumNumberOfNodes = clusterEnv.NumberOfProcesses;
  720. }
  721. }
  722. break;
  723. case ScheduleProcessPer.Core:
  724. job.MinimumNumberOfCores = job.MaximumNumberOfCores = clusterEnv.NumberOfProcesses;
  725. foreach (var task in tasks) {
  726. if (ShouldSetTaskParameters(task)) {
  727. task.MinimumNumberOfCores = task.MaximumNumberOfCores = clusterEnv.NumberOfProcesses;
  728. }
  729. }
  730. break;
  731. }
  732. }
  733. private void JobStateChanged(object sender, JobStateEventArg e) {
  734. string newJobState = String.Format("Job state for job {0} changed to {1}", e.JobId, GetStateDescription(e.NewState));
  735. OutputState(newJobState);
  736. SetStatus(newJobState);
  737. }
  738. private static void OutputState(string state) {
  739. var outWin = (IVsOutputWindow)CommonPackage.GetGlobalService(typeof(IVsOutputWindow));
  740. IVsOutputWindowPane pane;
  741. if (ErrorHandler.Succeeded(outWin.GetPane(VSConstants.GUID_OutWindowGeneralPane, out pane))) {
  742. pane.Activate();
  743. pane.OutputString(state + Environment.NewLine);
  744. }
  745. }
  746. private static void SetStatus(string text) {
  747. var statusBar = (IVsStatusbar)CommonPackage.GetGlobalService(typeof(SVsStatusbar));
  748. statusBar.SetText(text);
  749. }
  750. private static string GetStateDescription(JobState jobState) {
  751. switch (jobState) {
  752. case JobState.Canceled: return "Canceled";
  753. case JobState.Failed: return "Failed";
  754. case JobState.Running: return "Running";
  755. case JobState.Submitted: return "Submitted";
  756. case JobState.Validating: return "Validating";
  757. case JobState.Queued: return "Queued";
  758. case JobState.Finished: return "Finished";
  759. case JobState.Finishing: return "Finishing";
  760. case JobState.ExternalValidation: return "External Validation";
  761. case JobState.Configuring: return "Configuring";
  762. case JobState.Canceling: return "Cancelling";
  763. default: return "Unknown";
  764. }
  765. }
  766. private static bool CancelLaunch(string reason) {
  767. if (MessageBox.Show(
  768. String.Format("Publishing of project failed: {0}\r\nLaunch project anyway?", reason),
  769. "Publish project failed",
  770. MessageBoxButtons.YesNo,
  771. MessageBoxIcon.Warning) == DialogResult.No) {
  772. return true;
  773. }
  774. return false;
  775. }
  776. // This is duplicated throughout different assemblies in PythonTools, so search for it if you update it.
  777. internal static string GetPythonToolsInstallPath() {
  778. string path = Path.GetDirectoryName(typeof(PythonToolsPackage).Assembly.Location);
  779. if (File.Exists(Path.Combine(path, "PyDebugAttach.dll"))) {
  780. return path;
  781. }
  782. // running from the GAC in remote attach scenario. Look to the VS install dir.
  783. using (var configKey = OpenVisualStudioKey()) {
  784. var installDir = configKey.GetValue("InstallDir") as string;
  785. if (installDir != null) {
  786. var toolsPath = Path.Combine(installDir, "Extensions\\Microsoft\\Python Tools for Visual Studio\\1.0");
  787. if (File.Exists(Path.Combine(toolsPath, "PyDebugAttach.dll"))) {
  788. return toolsPath;
  789. }
  790. }
  791. }
  792. return null;
  793. }
  794. internal static string GetPythonHpcToolsInstallPath() {
  795. string path = Path.GetDirectoryName(typeof(HpcSupportPackage).Assembly.Location);
  796. if (File.Exists(Path.Combine(path, MpiShimExe))) {
  797. return path;
  798. }
  799. // running from the GAC in remote attach scenario. Look to the VS install dir.
  800. using (var configKey = OpenVisualStudioKey()) {
  801. var installDir = configKey.GetValue("InstallDir") as string;
  802. if (installDir != null) {
  803. var toolsPath = Path.Combine(installDir, "Extensions\\Microsoft\\Python Tools HPC Support\\1.0");
  804. if (File.Exists(Path.Combine(toolsPath, MpiShimExe))) {
  805. return toolsPath;
  806. }
  807. }
  808. }
  809. return null;
  810. }
  811. private static Win32.RegistryKey OpenVisualStudioKey() {
  812. if (HpcSupportPackage.Instance != null) {
  813. return HpcSupportPackage.Instance.ApplicationRegistryRoot;
  814. }
  815. if (Environment.Is64BitOperatingSystem) {
  816. #if DEV11
  817. return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("Software\\Microsoft\\VisualStudio\\11.0");
  818. #else
  819. return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("Software\\Microsoft\\VisualStudio\\10.0");
  820. #endif
  821. } else {
  822. #if DEV11
  823. return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\VisualStudio\\11.0");
  824. #else
  825. return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\VisualStudio\\10.0");
  826. #endif
  827. }
  828. }
  829. private static bool IsPathUnc(string path) {
  830. Uri uri;
  831. if (!string.IsNullOrWhiteSpace(path) && Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out uri)) {
  832. return uri.IsAbsoluteUri && uri.IsUnc;
  833. }
  834. return false;
  835. }
  836. }
  837. }