From def7aad83330f34b6ff77783a8c9e4c61ecbad75 Mon Sep 17 00:00:00 2001 From: Alex Bezdieniezhnykh Date: Sun, 15 Jun 2025 15:01:55 +0300 Subject: [PATCH] add resource check incorrect pass / hw handling in a loader --- Azaion.Common/DTO/RemoteCommand.cs | 1 + Azaion.Inference/remote_command_inf.pxd | 1 + Azaion.Inference/remote_command_inf.pyx | 1 + Azaion.Loader/api_client.pxd | 1 + Azaion.Loader/api_client.pyx | 11 ++- Azaion.Loader/main_loader.pyx | 6 +- Azaion.Loader/remote_command.pxd | 1 + Azaion.Loader/remote_command.pyx | 1 + Azaion.LoaderUI/ApiCredentials.cs | 12 ++- Azaion.LoaderUI/Azaion.LoaderUI.csproj | 2 + Azaion.LoaderUI/Constants.cs | 4 + Azaion.LoaderUI/Login.xaml.cs | 110 ++++++++++++++++++++++-- Azaion.LoaderUI/RemoteCommand.cs | 56 ++++++++++++ Azaion.Suite/App.xaml.cs | 2 +- 14 files changed, 193 insertions(+), 16 deletions(-) create mode 100644 Azaion.LoaderUI/RemoteCommand.cs diff --git a/Azaion.Common/DTO/RemoteCommand.cs b/Azaion.Common/DTO/RemoteCommand.cs index 5edeea9..1dc9508 100644 --- a/Azaion.Common/DTO/RemoteCommand.cs +++ b/Azaion.Common/DTO/RemoteCommand.cs @@ -39,6 +39,7 @@ public enum CommandType None = 0, Ok = 3, Login = 10, + CheckResource = 12, ListRequest = 15, ListFiles = 18, Load = 20, diff --git a/Azaion.Inference/remote_command_inf.pxd b/Azaion.Inference/remote_command_inf.pxd index 2571843..f9d4dd0 100644 --- a/Azaion.Inference/remote_command_inf.pxd +++ b/Azaion.Inference/remote_command_inf.pxd @@ -1,6 +1,7 @@ cdef enum CommandType: OK = 3 LOGIN = 10 + CHECK_RESOURCE = 12 LIST_REQUEST = 15 LIST_FILES = 18 LOAD = 20 diff --git a/Azaion.Inference/remote_command_inf.pyx b/Azaion.Inference/remote_command_inf.pyx index 03ff40d..ea78496 100644 --- a/Azaion.Inference/remote_command_inf.pyx +++ b/Azaion.Inference/remote_command_inf.pyx @@ -10,6 +10,7 @@ cdef class RemoteCommand: command_type_names = { 3: "OK", 10: "LOGIN", + 12: "CHECK_RESOURCE", 15: "LIST_REQUEST", 18: "LIST_FILES", 20: "LOAD", diff --git a/Azaion.Loader/api_client.pxd b/Azaion.Loader/api_client.pxd index 69bfec0..61e5342 100644 --- a/Azaion.Loader/api_client.pxd +++ b/Azaion.Loader/api_client.pxd @@ -16,6 +16,7 @@ cdef class ApiClient: cdef request(self, str method, str url, object payload, bint is_stream) cdef list_files(self, str folder, str search_file) + cdef check_resource(self) cdef load_bytes(self, str filename, str folder) cdef upload_file(self, str filename, bytes resource, str folder) cdef load_big_file_cdn(self, str folder, str big_part) diff --git a/Azaion.Loader/api_client.pyx b/Azaion.Loader/api_client.pyx index d3d1f03..5dadf61 100644 --- a/Azaion.Loader/api_client.pyx +++ b/Azaion.Loader/api_client.pyx @@ -47,10 +47,10 @@ cdef class ApiClient: token = response.json()["token"] self.set_token(token) except HTTPError as e: - constants.logerror(response.json()) + res = response.json() + constants.logerror(str(res)) if response.status_code == HTTPStatus.CONFLICT: - res = response.json() - raise Exception(res['Message']) + raise Exception(f"Error {res['ErrorCode']}: {res['Message']}") cdef set_token(self, str token): @@ -104,6 +104,11 @@ cdef class ApiClient: constants.log( f'Get files list by {folder}') return response.json() + cdef check_resource(self): + cdef str hardware = HardwareService.get_hardware_info() + payload = json.dumps({ "hardware": hardware }, indent=4) + response = self.request('post', f'{self.api_url}/resources/check', payload, is_stream=False) + cdef load_bytes(self, str filename, str folder): cdef str hardware = HardwareService.get_hardware_info() hw_hash = Security.get_hw_hash(hardware) diff --git a/Azaion.Loader/main_loader.pyx b/Azaion.Loader/main_loader.pyx index 8746480..f888a3c 100644 --- a/Azaion.Loader/main_loader.pyx +++ b/Azaion.Loader/main_loader.pyx @@ -44,6 +44,9 @@ cdef class CommandProcessor: if command.command_type == CommandType.LOGIN: self.api_client.set_credentials(Credentials.from_msgpack(command.data)) self.remote_handler.send(command.client_id, self.ok_response.serialize()) + elif command.command_type == CommandType.CHECK_RESOURCE: + self.api_client.check_resource() + self.remote_handler.send(command.client_id, RemoteCommand(CommandType.OK).serialize()) elif command.command_type == CommandType.LOAD: file_data = FileData.from_msgpack(command.data) file_bytes = self.api_client.load_bytes(file_data.filename, file_data.folder) @@ -61,9 +64,6 @@ cdef class CommandProcessor: data = UploadFileData.from_msgpack(command.data) file_bytes = self.api_client.upload_big_small_resource(data.resource, data.filename, data.folder) self.remote_handler.send(command.client_id, RemoteCommand(CommandType.OK).serialize()) - elif command.command_type == CommandType.EXIT: - t = Thread(target=self.stop) # non-block worker: - t.start() else: pass except Exception as e: diff --git a/Azaion.Loader/remote_command.pxd b/Azaion.Loader/remote_command.pxd index 2571843..f9d4dd0 100644 --- a/Azaion.Loader/remote_command.pxd +++ b/Azaion.Loader/remote_command.pxd @@ -1,6 +1,7 @@ cdef enum CommandType: OK = 3 LOGIN = 10 + CHECK_RESOURCE = 12 LIST_REQUEST = 15 LIST_FILES = 18 LOAD = 20 diff --git a/Azaion.Loader/remote_command.pyx b/Azaion.Loader/remote_command.pyx index 03ff40d..ea78496 100644 --- a/Azaion.Loader/remote_command.pyx +++ b/Azaion.Loader/remote_command.pyx @@ -10,6 +10,7 @@ cdef class RemoteCommand: command_type_names = { 3: "OK", 10: "LOGIN", + 12: "CHECK_RESOURCE", 15: "LIST_REQUEST", 18: "LIST_FILES", 20: "LOAD", diff --git a/Azaion.LoaderUI/ApiCredentials.cs b/Azaion.LoaderUI/ApiCredentials.cs index 360b3c6..eeee5f0 100644 --- a/Azaion.LoaderUI/ApiCredentials.cs +++ b/Azaion.LoaderUI/ApiCredentials.cs @@ -1,9 +1,15 @@ +using MessagePack; + namespace Azaion.LoaderUI; -public class ApiCredentials(string email, string pw) +[MessagePackObject] +public class ApiCredentials { - public string Email { get; set; } = email; - public string Password { get; set; } = pw; + [Key(nameof(Email))] + public string Email { get; set; } = null!; + + [Key(nameof(Password))] + public string Password { get; set; } = null!; public bool IsValid() => !string.IsNullOrWhiteSpace(Email) && !string.IsNullOrWhiteSpace(Password); diff --git a/Azaion.LoaderUI/Azaion.LoaderUI.csproj b/Azaion.LoaderUI/Azaion.LoaderUI.csproj index 74c4c88..09f0d5b 100644 --- a/Azaion.LoaderUI/Azaion.LoaderUI.csproj +++ b/Azaion.LoaderUI/Azaion.LoaderUI.csproj @@ -10,9 +10,11 @@ + + diff --git a/Azaion.LoaderUI/Constants.cs b/Azaion.LoaderUI/Constants.cs index acdc649..1171109 100644 --- a/Azaion.LoaderUI/Constants.cs +++ b/Azaion.LoaderUI/Constants.cs @@ -6,4 +6,8 @@ public static class Constants public const string API_URL = "https://api.azaion.com"; public const string AZAION_SUITE_EXE = "Azaion.Suite.exe"; public const string SUITE_FOLDER = "suite"; + public const string INFERENCE_EXE = "azaion-inference"; + public const string EXTERNAL_LOADER_PATH = "azaion-loader.exe"; + public const int EXTERNAL_LOADER_PORT = 5020; + public const string EXTERNAL_LOADER_HOST = "127.0.0.1"; } \ No newline at end of file diff --git a/Azaion.LoaderUI/Login.xaml.cs b/Azaion.LoaderUI/Login.xaml.cs index 431f3e7..06d1dde 100644 --- a/Azaion.LoaderUI/Login.xaml.cs +++ b/Azaion.LoaderUI/Login.xaml.cs @@ -1,10 +1,15 @@ using System.Diagnostics; using System.IO; +using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Media; +using MessagePack; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using NetMQ; +using NetMQ.Sockets; namespace Azaion.LoaderUI; @@ -23,17 +28,40 @@ public partial class Login InitializeComponent(); } + private void SetControlsStatus(bool isLoading) + { + LoginBtn.IsEnabled = !isLoading; + TbEmail.IsEnabled = !isLoading; + TbPassword.IsEnabled = !isLoading; + LoginBtn.Cursor = isLoading ? Cursors.Wait : Cursors.Arrow; + Cursor = isLoading ? Cursors.Wait : Cursors.Arrow; + } + private async void LoginClick(object sender, RoutedEventArgs e) { - var creds = new ApiCredentials(TbEmail.Text, TbPassword.Password); + var creds = new ApiCredentials + { + Email = TbEmail.Text, + Password = TbPassword.Password + }; if (!creds.IsValid()) return; - LoginBtn.Cursor = Cursors.Wait; - Cursor = Cursors.Wait; - + SetControlsStatus(isLoading: true); _azaionApi.Login(creds); - + try + { + Validate(creds); + } + catch (Exception exception) + { + _logger.LogError(exception, exception.Message); + TbStatus.Foreground = Brushes.Red; + TbStatus.Text = exception.Message; + SetControlsStatus(isLoading: false); + return; + } + TbStatus.Foreground = Brushes.Black; var installerVersion = await GetInstallerVer(); var localVersion = GetLocalVer(); @@ -44,12 +72,81 @@ public partial class Login TbStatus.Text = $"Installed {installerVersion}!"; } else - TbStatus.Text = $"Your version is up to date!"; + TbStatus.Text = "Your version is up to date!"; Process.Start(Constants.AZAION_SUITE_EXE, $"-e {creds.Email} -p {creds.Password}"); + await Task.Delay(800); + TbStatus.Text = "Loading..."; + while (!Process.GetProcessesByName(Constants.INFERENCE_EXE).Any()) + await Task.Delay(500); + await Task.Delay(500); Close(); } + private void Validate(ApiCredentials creds) + { + var dealer = new DealerSocket(); + try + { + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = Constants.EXTERNAL_LOADER_PATH, + Arguments = $"--port {Constants.EXTERNAL_LOADER_PORT} --api {Constants.API_URL}", + CreateNoWindow = true + }; + process.Start(); + dealer.Options.Identity = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")); + dealer.Connect($"tcp://{Constants.EXTERNAL_LOADER_HOST}:{Constants.EXTERNAL_LOADER_PORT}"); + + var result = SendCommand(dealer, RemoteCommand.Create(CommandType.Login, creds)); + if (result.CommandType != CommandType.Ok) + throw new Exception(result.Message); + + result = SendCommand(dealer, RemoteCommand.Create(CommandType.CheckResource)); + if (result.CommandType != CommandType.Ok) + throw new Exception(result.Message); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + throw; + } + finally + { + SendCommand(dealer, RemoteCommand.Create(CommandType.Exit)); + dealer.Close(); + dealer.Dispose(); + } + } + + private RemoteCommand SendCommand(DealerSocket dealer, RemoteCommand command, int retryCount = 30, int retryDelayMs = 800) + { + try + { + dealer.SendFrame(MessagePackSerializer.Serialize(command)); + + var tryNum = 0; + while (tryNum++ < retryCount) + { + if (!dealer.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(retryDelayMs), out var bytes)) + continue; + var res = MessagePackSerializer.Deserialize(bytes); + if (res.CommandType == CommandType.Error) + throw new Exception(res.Message); + return res; + } + + throw new Exception($"Sent {command} {retryCount} times, with wait time {retryDelayMs}ms for each call. No response from client."); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + throw; + } + } + + private async Task DownloadAndRunInstaller() { var (installerName, stream) = await _azaionApi.DownloadInstaller(_dirConfig?.SuiteInstallerDirectory ?? ""); @@ -65,6 +162,7 @@ public partial class Login var process = Process.Start(processInfo); await process!.WaitForExitAsync(); + File.Delete(installerName); } private async Task GetInstallerVer() diff --git a/Azaion.LoaderUI/RemoteCommand.cs b/Azaion.LoaderUI/RemoteCommand.cs new file mode 100644 index 0000000..09eb428 --- /dev/null +++ b/Azaion.LoaderUI/RemoteCommand.cs @@ -0,0 +1,56 @@ +using MessagePack; + +namespace Azaion.LoaderUI; + +[MessagePackObject] +public class RemoteCommand(CommandType commandType, byte[]? data = null, string? message = null) +{ + [Key("CommandType")] + public CommandType CommandType { get; set; } = commandType; + + [Key("Data")] + public byte[]? Data { get; set; } = data; + + [Key("Message")] + public string? Message { get; set; } = message; + + public static RemoteCommand Create(CommandType commandType) => + new(commandType); + + public static RemoteCommand Create(CommandType commandType, T data, string? message = null) where T : class => + new(commandType, MessagePackSerializer.Serialize(data), message); + + public override string ToString() => $"({CommandType.ToString().ToUpper()}: Data: {Data?.Length ?? 0} bytes. Message: {Message})"; +} + +[MessagePackObject] +public class LoadFileData(string filename, string? folder = null ) +{ + [Key(nameof(Folder))] + public string? Folder { get; set; } = folder; + + [Key(nameof(Filename))] + public string Filename { get; set; } = filename; +} + + +public enum CommandType +{ + None = 0, + Ok = 3, + Login = 10, + CheckResource = 12, + ListRequest = 15, + ListFiles = 18, + Load = 20, + LoadBigSmall = 22, + UploadBigSmall = 24, + DataBytes = 25, + Inference = 30, + InferenceData = 35, + StopInference = 40, + AIAvailabilityCheck = 80, + AIAvailabilityResult = 85, + Error = 90, + Exit = 100, +} \ No newline at end of file diff --git a/Azaion.Suite/App.xaml.cs b/Azaion.Suite/App.xaml.cs index 7ae0381..12b71d1 100644 --- a/Azaion.Suite/App.xaml.cs +++ b/Azaion.Suite/App.xaml.cs @@ -111,7 +111,7 @@ public partial class App _loaderClient = new LoaderClient(initConfig.LoaderClientConfig, Log.Logger, _mainCTokenSource.Token); _loaderClient.StartClient(); - _loaderClient.Connect(); //Client app should be already started by LoaderUI + _loaderClient.Connect(); _loaderClient.Login(credentials); var azaionApi = new AzaionApi(Log.Logger, new HttpClient { BaseAddress = new Uri(initConfig.InferenceClientConfig.ApiUrl) }, _cache, credentials);