using Ionic.Zip; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core; using Tango.Core.Components; using Tango.Core.DB; using Tango.Core.DI; using Tango.Core.ExtensionMethods; using Tango.Core.Threading; using Tango.FileSystem; using Tango.FSE.BL; using Tango.FSE.BL.Web; using Tango.FSE.Common; using Tango.FSE.Common.Authentication; using Tango.FSE.Common.Connection; using Tango.FSE.Common.FileSystem; using Tango.FSE.Common.MachineUpdates; using Tango.FSE.Common.RemoteUpgrade; using Tango.FSE.Web.Messages; using Tango.Integration.Operation; using Tango.PPC.Common.Publish; using Tango.PPC.Shared.RemoteUpgrade; using Tango.PPC.Shared.Updates; using Tango.SQLExaminer; using Tango.Transport; using Tango.Transport.Web; using Tango.Web; namespace Tango.FSE.UI.RemoteUpgrade { /// /// Represents the default implementation. /// /// public class DefaultRemoteUpgradeManager : FSEExtendedObject, IRemoteUpgradeManager { [TangoInject] private IMachineProvider MachineProvider { get; set; } [TangoInject] private FSEWebClient WebClient { get; set; } [TangoInject] private IAuthenticationProvider AuthenticationProvider { get; set; } [TangoInject] private IFileSystemProvider FileSystemProvider { get; set; } [TangoInject] private FSEServicesContainer Services { get; set; } /// /// Initializes a new instance of the class. /// /// The authentication provider. /// The machine provider. /// The web client. public DefaultRemoteUpgradeManager() { } /// /// Creates a Tango Update Package for the current connected machine. /// /// The tango version. /// The file path. /// public Task CreateTupFile(TangoVersion tangoVersion, string targetFilePath) { if (MachineProvider.Machine == null) { throw new InvalidOperationException("Could not create a TUP file while machine is disconnected."); } return CreateTupFile(tangoVersion, MachineProvider.Machine.SerialNumber); } /// /// Creates a Tango Update Package for specified machine. /// /// The tango version. /// The machine serial number. /// The file path. /// public Task CreateTupFile(TangoVersion tangoVersion, string serialNumber, string targetFilePath) { AuthenticationProvider.ThrowIfNoPermission(Permissions.FSE_RemoteUpgradeOffline); Thread thread = null; RemoteUpgradeHandler handler = null; handler = new RemoteUpgradeHandler(() => { if (handler.CanAbort) { try { thread.Abort(); } catch { } } }); thread = ThreadFactory.StartNew(() => { Thread.Sleep(100); String tempDbName = "Tango_TUP"; var tempPackageFolder = TemporaryManager.CreateFolder(); String tempBackupFolder = "C:\\FSE_TUP"; String tempBackupFile = Path.Combine(tempBackupFolder, tempDbName + ".bak"); var tempZipFile = TemporaryManager.CreateImaginaryFile(); DbManager dbManager = null; LogManager.Log("Generating tup file..."); LogManager.Log($"Tup file: '{targetFilePath}.'"); LogManager.Log($"Temporary db name: '{tempDbName}'."); LogManager.Log($"Temporary package folder: '{tempPackageFolder}'."); LogManager.Log($"Temporary db backup folder: '{tempBackupFolder}'."); LogManager.Log($"Temporary db backup file: '{tempBackupFile}'."); LogManager.Log($"Temporary zip file: '{tempZipFile}'."); try { LogManager.Log("Initializing..."); handler.UpdateProgress("Initializing..."); Tango.Core.DataSource localDataSource = new Tango.Core.DataSource() { Address = "localhost\\SQLEXPRESS", IntegratedSecurity = true, Type = DataSourceType.SQLServer, Catalog = null, }; try { LogManager.Log($"Trying to connect via SQLEXPRESS:\n{localDataSource.ToJsonString()}"); dbManager = DbManager.FromDataSource(localDataSource); } catch (Exception ex) { try { LogManager.Log(ex, "Could not connect using SQLEXPRESS. Trying local DB..."); CmdCommand command = new CmdCommand("sqllocaldb", "start \"MSSQLLocalDB\""); command.Timeout = TimeSpan.FromSeconds(30); var result = command.Run().Result; LogManager.Log("local DB started. Retrieving instance information..."); command = new CmdCommand("sqllocaldb", "info \"MSSQLLocalDB\""); result = command.Run().Result; String pattern = "np:.+"; Regex reg = new Regex(pattern); var match = reg.Match(result.StandardOutput); String address = match.ToString(); if (address.Contains("np:")) { localDataSource.Address = address; address = address.Trim().Replace("\r", ""); } else { throw new ArgumentException("Could not parse LocalDB address string."); } LogManager.Log($"Trying to connect via LocalDB:\n{localDataSource.ToJsonString()}"); dbManager = DbManager.FromDataSource(localDataSource); } catch (Exception x) { LogManager.Log(x, "Could not find any database service for this operation."); throw x; } } handler.UpdateProgress($"Downloading Tango version '{tangoVersion.VersionAndTag}'..."); LogManager.Log("Connecting to machine service..."); LogManager.Log("Requesting version download from machine service..."); var response = WebClient.DownloadTangoVersion(new DownloadTangoVersionRequest() { TangoVersionGuid = tangoVersion.Guid }).Result; LogManager.Log($"Machine service response:\n{response.ToJsonString()}"); var remoteDataSource = response.DataSource; LogManager.Log($"Checking for a cached tup file for version '{tangoVersion.VersionAndTag}'..."); bool hasCachedFile = false; try { if (Services.TangoVersionsService.IsCachedTupFileExists(tangoVersion)) { Services.TangoVersionsService.DownloadCachedTupFile(tangoVersion, tempZipFile).Wait(); hasCachedFile = true; } } catch (Exception ex) { LogManager.Log(ex, "Error retrieving tup file cache."); } if (!hasCachedFile) { LogManager.Log($"Cached tup file not found. Starting blob download..."); using (AutoFileDownloader downloader = new AutoFileDownloader(response.BlobAddress, response.CdnAddress, tempZipFile)) { downloader.Progress += (x, e) => { handler.UpdateProgress($"Downloading Tango version '{tangoVersion.VersionAndTag}'...", false, e.Current, e.Total); }; downloader.ResolveMode().GetAwaiter().GetResult(); LogManager.Log($"Downloading Tango version from: '{downloader.Address}'"); downloader.Download().Wait(); try { LogManager.Log("blob download completed. Caching tup file..."); Services.TangoVersionsService.UploadCachedTupFile(tangoVersion, tempZipFile).Wait(); } catch (Exception ex) { LogManager.Log(ex, "Error caching tup file."); } } } LogManager.Log("Extracting version package..."); handler.UpdateProgress("Extracting package..."); using (ZipFile zip = new ZipFile(tempZipFile)) { int currentEntry = 0; zip.ExtractProgress += (x, args) => { if (args.EventType == ZipProgressEventType.Extracting_AfterExtractEntry) { handler.UpdateProgress("Extracting package...", false, currentEntry++, zip.Entries.Count); } }; zip.ExtractAll(tempPackageFolder); } handler.UpdateProgress("Extracting version information..."); LogManager.Log("Extracting publish information..."); PublishInfo publishInfo = PublishInfo.FromJson(File.ReadAllText(Path.Combine(tempPackageFolder, "version.json"))); LogManager.Log($"Publish Information:\n{publishInfo}"); LogManager.Log("Modifying publish information to custom tup file..."); publishInfo.IsMachineTupPackage = true; publishInfo.MachineSerialNumber = serialNumber; publishInfo.MachineDeploymentSlot = (DeploymentSlot)Enum.Parse(typeof(DeploymentSlot), AuthenticationProvider.CurrentEnvironment.Name); handler.UpdateProgress("Creating temporary database..."); LogManager.Log($"Creating temporary db backup directory '{tempBackupFolder}'"); Directory.CreateDirectory(tempBackupFolder); LogManager.Log($"Creating new database: '{tempDbName}'"); //Create temp db dbManager.Create(tempDbName, Path.Combine(tempBackupFolder, tempDbName + ".mdf")); handler.UpdateProgress("Generating database snapshot..."); LogManager.Log("Starting database synchronization..."); Thread.Sleep(2000); localDataSource.Catalog = tempDbName; ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner( Path.Combine(tempPackageFolder, "Provision Scripts", "config.xml"), Path.Combine(tempPackageFolder, "Provision Scripts"), remoteDataSource, localDataSource, serialNumber); runner.ScriptExecuting += (x, item) => { LogManager.Log($"Executing script '{item.FileName}'..."); handler.UpdateProgress($"{item.Name}..."); }; runner.Log += (x, log) => { LogManager.Log(log); }; runner.Run().GetAwaiter().GetResult(); handler.UpdateProgress("Generating database snapshot..."); if (File.Exists(tempBackupFile)) { LogManager.Log($"Deleting file '{tempBackupFile}'"); File.Delete(tempBackupFile); } LogManager.Log($"Generating backup for '{tempDbName}' to '{tempBackupFile}'..."); dbManager.Backup(tempDbName, tempBackupFile); handler.UpdateProgress("Injecting database snapshot to PPC package..."); using (ZipFile zip = new ZipFile(tempZipFile)) { LogManager.Log($"Injecting file '{tempBackupFile}' to original package at '{tempZipFile}'..."); zip.AddFile(tempBackupFile, "/"); LogManager.Log($"Injecting modified publish information..."); zip.UpdateEntry("version.json", publishInfo.ToJson()); zip.Save(); } LogManager.Log($"Copying '{tempZipFile}' to '{targetFilePath}'..."); File.Copy(tempZipFile, targetFilePath, true); handler.UpdateProgress("Package generated successfully.", false, 100, 100); LogManager.Log("TUP file generation completed successfully."); handler.RaiseCompleted(); } catch (ThreadAbortException ex) { LogManager.Log(ex, "TUP file generation aborted."); handler.UpdateProgress($"Package generation aborted.", false, 0, 100); handler.RaiseAborted(); } catch (Exception ex) { LogManager.Log(ex, "TUP file generation failed."); handler.UpdateProgress($"Failed to generated package. {ex.GetFirstIfAggregate().FlattenMessage()}", false, 0, 100); handler.RaiseFailed(ex); } finally { LogManager.Log($"Removing '{tempZipFile}'."); tempZipFile.Delete(); LogManager.Log($"Removing '{tempPackageFolder}'."); tempPackageFolder.Delete(); try { LogManager.Log($"Removing database '{tempDbName}'."); dbManager.SetOffline(tempDbName); dbManager.SetOnline(tempDbName); dbManager.Delete(tempDbName); dbManager.Dispose(); } catch (Exception ex) { LogManager.Log(ex, $"Error removing temp database '{tempDbName}'."); } try { LogManager.Log($"Removing '{tempBackupFolder}'."); Directory.Delete(tempBackupFolder, true); } catch (Exception ex) { LogManager.Log(ex, $"Error removing folder '{tempBackupFolder}'."); } } }); return Task.FromResult(handler); } /// /// Creates a firmware upgrade package file. /// /// The tango version. /// The file path. /// public Task CreateTfpFile(TangoVersion tangoVersion, string targetFilePath) { AuthenticationProvider.ThrowIfNoPermission(Permissions.FSE_RemoteUpgradeOffline); Thread thread = null; RemoteUpgradeHandler handler = null; handler = new RemoteUpgradeHandler(() => { if (handler.CanAbort) { try { thread.Abort(); } catch { } } }); thread = ThreadFactory.StartNew(() => { Thread.Sleep(100); LogManager.Log("Generating tfp file..."); LogManager.Log($"Tfp file: '{targetFilePath}.'"); try { LogManager.Log("Initializing..."); handler.UpdateProgress("Initializing..."); handler.UpdateProgress($"Downloading firmware version '{tangoVersion.FirmwareVersion}'..."); LogManager.Log("Connecting to machine service..."); LogManager.Log("Requesting version download from machine service..."); var response = WebClient.DownloadTangoVersion(new DownloadTangoVersionRequest() { TangoVersionGuid = tangoVersion.Guid }).Result; LogManager.Log($"Machine service response:\n{response.ToJsonString()}"); LogManager.Log($"Checking for a cached tfp file for version '{tangoVersion.Version}'..."); bool hasCachedFile = false; try { if (Services.TangoVersionsService.IsCachedTfpFileExists(tangoVersion)) { LogManager.Log("cached tfp file found. Copying to temporary file and skipping download..."); Services.TangoVersionsService.DownloadCachedTfpFile(tangoVersion, targetFilePath).Wait(); hasCachedFile = true; } } catch (Exception ex) { LogManager.Log(ex, "Error retrieving cached tfp file."); } if (!hasCachedFile) { LogManager.Log($"Cached tfp file not found. Extracting firmware from remote blob..."); using (StorageBlobStream blobZipStream = new StorageBlobStream(response.BlobAddress)) { using (ZipFile zip = ZipFile.Read(blobZipStream.OpenRead())) { var tfpEntry = zip.Entries.SingleOrDefault(x => x.FileName == "firmware_package.tfp"); using (FileStream targetStream = new FileStream(targetFilePath, FileMode.Create)) { tfpEntry.Extract(targetStream); } } } try { LogManager.Log("blob download completed. Caching tfp file..."); Services.TangoVersionsService.UploadCachedTfpFile(tangoVersion, targetFilePath).Wait(); } catch (Exception ex) { LogManager.Log(ex, "Error caching tfp file."); } } handler.UpdateProgress("Package generated successfully.", false, 100, 100); LogManager.Log("TFP file generation completed successfully."); handler.RaiseCompleted(); } catch (ThreadAbortException ex) { LogManager.Log(ex, "TFP file generation aborted."); handler.UpdateProgress($"Package generation aborted.", false, 0, 100); handler.RaiseAborted(); } catch (Exception ex) { LogManager.Log(ex, "TFP file generation failed."); handler.UpdateProgress($"Failed to generated firmware package. {ex.GetFirstIfAggregate().FlattenMessage()}", false, 0, 100); handler.RaiseFailed(ex); } finally { //Nothing right now.. } }); return Task.FromResult(handler); } /// /// Performs a remote application upgrade using the specified .tup file. /// /// The .tup file. /// Specify whether to upgrade the firmware while doing the complete upgrade. (SetupFirmware/FPGA) /// public Task PerformRemoteApplicationUpgrade(string tupFile, bool upgradeFirmware = true) { AuthenticationProvider.ThrowIfNoPermission(Permissions.FSE_RemoteUpgradeOnline); Thread thread = null; RemoteUpgradeHandler handler = null; handler = new RemoteUpgradeHandler(() => { if (handler.CanAbort) { try { thread.Abort(); } catch { } } }); thread = ThreadFactory.StartNew(() => { try { Thread.Sleep(100); LogManager.Log($"Starting remote application upgrade for the currently connected machine '{MachineProvider.Machine.SerialNumber}'..."); handler.UpdateProgress("Validating machine connection state..."); LogManager.Log("Validating machine connection state..."); if (!MachineProvider.IsConnected) { throw new InvalidOperationException("Machine is disconnected."); } if (!MachineProvider.ConnectionType.IsRemote()) { throw new InvalidOperationException("The current machine connection does not support remote application upgrade."); } if (!File.Exists(tupFile)) { throw new FileNotFoundException("Could not locate the specified package file."); } handler.UpdateProgress("Uploading application package file..."); LogManager.Log("Retrieving remote temporary folder..."); var remoteTempFolder = FileSystemProvider.GetFolder("%temp%").Result as FileSystemItem; var remoteTempFile = Path.Combine(remoteTempFolder.Path, Path.GetFileName(Path.GetTempFileName())); LogManager.Log("Uploading tup file to remote machine..."); var uploadHandler = FileSystemProvider.Upload(tupFile, remoteTempFile, true).Result; uploadHandler.ProgressChanged += (_, e) => { handler.UpdateProgress("Uploading application package file...", false, e.Progress.Value, e.Progress.Maximum); }; var status = uploadHandler.WaitForCompletion().Result; LogManager.Log("Tup upload completed successfully. Sending remote upgrade request..."); TaskCompletionSource completionSource = new TaskCompletionSource(); handler.CanAbort = false; MachineProvider.MachineOperator.SendGenericContinuousRequest( new StartRemoteApplicationUpgradeRequest() { RemoteTupFilePath = remoteTempFile, SetupFirmware = upgradeFirmware, SetupFPGA = upgradeFirmware }, new TransportContinuousRequestConfig() { ShouldLog = true, Timeout = TimeSpan.FromSeconds(10), ContinuousTimeout = TimeSpan.FromSeconds(60) }).Subscribe((response) => { //Response.. handler.UpdateProgress(response.Progress); }, (ex) => { //Failed completionSource.SetException(ex); }, () => { //Completed completionSource.SetResult(true); }); var waitForCompletion = completionSource.Task.Result; LogManager.Log("Remote application upgrade completed successfully."); handler.UpdateProgress($"Remote application upgrade completed successfully.", false, 100, 100); handler.RaiseCompleted(); } catch (ThreadAbortException ex) { LogManager.Log(ex, "Remote application upgrade aborted."); handler.UpdateProgress($"Remote application upgrade aborted.", false, 0, 100); handler.RaiseAborted(); } catch (Exception ex) { handler.UpdateProgress($"Remote application upgrade failed. {ex.GetFirstIfAggregate().FlattenMessage()}", false, 0, 100); handler.RaiseFailed(ex); } }); return Task.FromResult(handler); } /// /// Performs a remote firmware upgrade using the specified .tfp file. /// /// The .tfp file. /// public Task PerformRemoteFirmwareUpgrade(string tfpFile) { AuthenticationProvider.ThrowIfNoPermission(Permissions.FSE_RemoteUpgradeOnline); Thread thread = null; RemoteUpgradeHandler handler = null; handler = new RemoteUpgradeHandler(() => { if (handler.CanAbort) { try { thread.Abort(); } catch { } } }); thread = ThreadFactory.StartNew(() => { try { Thread.Sleep(100); LogManager.Log($"Starting remote firmware upgrade for the currently connected machine '{MachineProvider.Machine.SerialNumber}'..."); handler.UpdateProgress("Validating machine connection state..."); LogManager.Log("Validating machine connection state..."); if (!MachineProvider.IsConnected) { throw new InvalidOperationException("Machine is disconnected."); } if (!MachineProvider.ConnectionType.IsRemote()) { throw new InvalidOperationException("The current machine connection does not support remote application upgrade."); } if (!File.Exists(tfpFile)) { throw new FileNotFoundException("Could not locate the specified package file."); } LogManager.Log("Validating firmware package file..."); using (FileStream s = File.OpenRead(tfpFile)) { var package = MachineProvider.MachineOperator.GetFirmwarePackageInfo(s).Result; package.Validate(); } handler.UpdateProgress("Uploading firmware package file..."); LogManager.Log("Retrieving remote temporary folder..."); var remoteTempFolder = FileSystemProvider.GetFolder("%temp%").Result as FileSystemItem; var remoteTempFile = Path.Combine(remoteTempFolder.Path, Path.GetFileName(Path.GetTempFileName())); LogManager.Log("Uploading tfp file to remote machine..."); var uploadHandler = FileSystemProvider.Upload(tfpFile, remoteTempFile, true).Result; uploadHandler.ProgressChanged += (_, e) => { handler.UpdateProgress("Uploading firmware package file...", false, e.Progress.Value, e.Progress.Maximum); }; var status = uploadHandler.WaitForCompletion().Result; LogManager.Log("Tfp upload completed successfully. Sending remote upgrade request..."); TaskCompletionSource completionSource = new TaskCompletionSource(); handler.CanAbort = false; MachineProvider.MachineOperator.SendGenericContinuousRequest( new StartRemoteFirmwareUpgradeRequest() { RemoteTfpFilePath = remoteTempFile, }, new TransportContinuousRequestConfig() { ShouldLog = true, Timeout = TimeSpan.FromSeconds(10), ContinuousTimeout = TimeSpan.FromSeconds(60) }).Subscribe((response) => { //Response.. handler.UpdateProgress(response.Progress); }, (ex) => { //Failed completionSource.SetException(ex); }, () => { //Completed completionSource.SetResult(true); }); var waitForCompletion = completionSource.Task.Result; LogManager.Log("Remote firmware upgrade completed successfully."); handler.UpdateProgress($"Remote firmware upgrade completed successfully.", false, 100, 100); handler.RaiseCompleted(); } catch (ThreadAbortException ex) { LogManager.Log(ex, "Remote firmware upgrade aborted."); handler.UpdateProgress($"Remote firmware upgrade aborted.", false, 0, 100); handler.RaiseAborted(); } catch (Exception ex) { handler.UpdateProgress($"Remote firmware upgrade failed. {ex.GetFirstIfAggregate().FlattenMessage()}", false, 0, 100); handler.RaiseFailed(ex); } }); return Task.FromResult(handler); } } }