From 2ecbc9bfd49b7aaec5d27c2a0106a9c1c408eb0d Mon Sep 17 00:00:00 2001 From: Alex Bezdieniezhnykh Date: Sun, 16 Feb 2025 16:35:52 +0200 Subject: [PATCH] move zmq port to config file for C# and python --- Azaion.Common/DTO/Config/AppConfig.cs | 2 +- Azaion.Common/Services/InferenceService.cs | 11 +- Azaion.CommonSecurity/DTO/ApiConfig.cs | 9 -- Azaion.CommonSecurity/DTO/PythonConfig.cs | 11 ++ Azaion.CommonSecurity/DTO/SecureAppConfig.cs | 2 +- Azaion.CommonSecurity/DTO/User.cs | 15 --- Azaion.CommonSecurity/SecurityConstants.cs | 26 ++--- .../Services/AzaionApiClient.cs | 110 ------------------ .../Services/PythonResourceLoader.cs | 7 +- Azaion.Inference/api_client.pxd | 2 +- Azaion.Inference/build.cmd | 24 ++++ Azaion.Inference/config.yaml | 1 + Azaion.Inference/constants.pxd | 2 +- Azaion.Inference/constants.pyx | 6 +- Azaion.Inference/remote_command_handler.pyx | 6 +- Azaion.Suite/App.xaml.cs | 19 +-- Azaion.Suite/config.json | 7 +- 17 files changed, 80 insertions(+), 180 deletions(-) delete mode 100644 Azaion.CommonSecurity/DTO/ApiConfig.cs create mode 100644 Azaion.CommonSecurity/DTO/PythonConfig.cs delete mode 100644 Azaion.CommonSecurity/Services/AzaionApiClient.cs create mode 100644 Azaion.Inference/build.cmd create mode 100644 Azaion.Inference/config.yaml diff --git a/Azaion.Common/DTO/Config/AppConfig.cs b/Azaion.Common/DTO/Config/AppConfig.cs index c52e050..c7c7545 100644 --- a/Azaion.Common/DTO/Config/AppConfig.cs +++ b/Azaion.Common/DTO/Config/AppConfig.cs @@ -8,7 +8,7 @@ namespace Azaion.Common.DTO.Config; public class AppConfig { - public ApiConfig ApiConfig { get; set; } = null!; + public PythonConfig PythonConfig { get; set; } = null!; public QueueConfig QueueConfig { get; set; } = null!; diff --git a/Azaion.Common/Services/InferenceService.cs b/Azaion.Common/Services/InferenceService.cs index 5cc9f02..10f4eed 100644 --- a/Azaion.Common/Services/InferenceService.cs +++ b/Azaion.Common/Services/InferenceService.cs @@ -2,13 +2,12 @@ using Azaion.Common.Database; using Azaion.Common.DTO.Config; using Azaion.CommonSecurity; +using Azaion.CommonSecurity.DTO; using Azaion.CommonSecurity.DTO.Commands; -using MessagePack; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NetMQ; using NetMQ.Sockets; -using Newtonsoft.Json; namespace Azaion.Common.Services; @@ -17,16 +16,18 @@ public interface IInferenceService Task RunInference(List mediaPaths, Func processAnnotation, CancellationToken ct = default); } -public class PythonInferenceService(ILogger logger, IOptions aiConfigOptions) : IInferenceService +public class PythonInferenceService(ILogger logger, IOptions pythonConfigOptions, IOptions aiConfigOptions) : IInferenceService { public async Task RunInference(List mediaPaths, Func processAnnotation, CancellationToken ct = default) { + var pythonConfig = pythonConfigOptions.Value; + var aiConfig = aiConfigOptions.Value; + using var dealer = new DealerSocket(); var clientId = Guid.NewGuid(); dealer.Options.Identity = Encoding.UTF8.GetBytes(clientId.ToString("N")); - dealer.Connect($"tcp://{SecurityConstants.ZMQ_HOST}:{SecurityConstants.ZMQ_PORT}"); + dealer.Connect($"tcp://{pythonConfig.ZeroMqHost}:{pythonConfig.ZeroMqPort}"); - var aiConfig = aiConfigOptions.Value; aiConfig.Paths = mediaPaths; dealer.SendFrame(RemoteCommand.Serialize(CommandType.Inference, aiConfig)); diff --git a/Azaion.CommonSecurity/DTO/ApiConfig.cs b/Azaion.CommonSecurity/DTO/ApiConfig.cs deleted file mode 100644 index c6d4056..0000000 --- a/Azaion.CommonSecurity/DTO/ApiConfig.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Azaion.CommonSecurity.DTO; - -public class ApiConfig -{ - public string Url { get; set; } = null!; - public int RetryCount {get;set;} - public double TimeoutSeconds { get; set; } - public string ResourcesFolder { get; set; } = ""; -} diff --git a/Azaion.CommonSecurity/DTO/PythonConfig.cs b/Azaion.CommonSecurity/DTO/PythonConfig.cs new file mode 100644 index 0000000..20f5c65 --- /dev/null +++ b/Azaion.CommonSecurity/DTO/PythonConfig.cs @@ -0,0 +1,11 @@ +namespace Azaion.CommonSecurity.DTO; + +public class PythonConfig +{ + public string ZeroMqHost { get; set; } + public int ZeroMqPort { get; set; } + public double OneTryTimeoutSeconds { get; set; } + public int RetryCount {get;set;} + + public string ResourcesFolder { get; set; } = ""; +} diff --git a/Azaion.CommonSecurity/DTO/SecureAppConfig.cs b/Azaion.CommonSecurity/DTO/SecureAppConfig.cs index cd4e723..3c16cd3 100644 --- a/Azaion.CommonSecurity/DTO/SecureAppConfig.cs +++ b/Azaion.CommonSecurity/DTO/SecureAppConfig.cs @@ -2,5 +2,5 @@ public class SecureAppConfig { - public ApiConfig ApiConfig { get; set; } = null!; + public PythonConfig PythonConfig { get; set; } = null!; } \ No newline at end of file diff --git a/Azaion.CommonSecurity/DTO/User.cs b/Azaion.CommonSecurity/DTO/User.cs index dfb09f8..bb2a2ba 100644 --- a/Azaion.CommonSecurity/DTO/User.cs +++ b/Azaion.CommonSecurity/DTO/User.cs @@ -1,4 +1,3 @@ -using System.Security.Claims; using MessagePack; namespace Azaion.CommonSecurity.DTO; @@ -9,18 +8,4 @@ public class User [Key("i")] public string Id { get; set; } = ""; [Key("e")] public string Email { get; set; } = ""; [Key("r")]public RoleEnum Role { get; set; } - - //For deserializing - public User(){} - - public User(IEnumerable claims) - { - var claimDict = claims.ToDictionary(x => x.Type, x => x.Value); - - Id = claimDict[SecurityConstants.CLAIM_NAME_ID]; - Email = claimDict[SecurityConstants.CLAIM_EMAIL]; - if (!Enum.TryParse(claimDict[SecurityConstants.CLAIM_ROLE], out RoleEnum role)) - role = RoleEnum.None; - Role = role; - } } \ No newline at end of file diff --git a/Azaion.CommonSecurity/SecurityConstants.cs b/Azaion.CommonSecurity/SecurityConstants.cs index 3f41ceb..944116f 100644 --- a/Azaion.CommonSecurity/SecurityConstants.cs +++ b/Azaion.CommonSecurity/SecurityConstants.cs @@ -6,23 +6,13 @@ public class SecurityConstants public const string DUMMY_DIR = "dummy"; - #region ApiConfig - - public const string DEFAULT_API_URL = "https://api.azaion.com/"; - public const int DEFAULT_API_RETRY_COUNT = 3; - public const int DEFAULT_API_TIMEOUT_SECONDS = 40; - - public const string CLAIM_NAME_ID = "nameid"; - public const string CLAIM_EMAIL = "unique_name"; - public const string CLAIM_ROLE = "role"; - - #endregion ApiConfig - - #region SocketClient - public const string ZMQ_HOST = "127.0.0.1"; - public const int ZMQ_PORT = 5127; - - #endregion SocketClient - + #region PythonConfig public const string AZAION_INFERENCE_PATH = "azaion-inference.exe"; + + public const string DEFAULT_ZMQ_HOST = "127.0.0.1"; + public const int DEFAULT_ZMQ_PORT = 5127; + public const int DEFAULT_RETRY_COUNT = 25; + public const int DEFAULT_TIMEOUT_SECONDS = 5; + + #endregion PythonConfig } \ No newline at end of file diff --git a/Azaion.CommonSecurity/Services/AzaionApiClient.cs b/Azaion.CommonSecurity/Services/AzaionApiClient.cs deleted file mode 100644 index 18f4b7b..0000000 --- a/Azaion.CommonSecurity/Services/AzaionApiClient.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Net; -using System.Net.Http.Headers; -using System.Security; -using System.Text; -using Azaion.CommonSecurity.DTO; -using Newtonsoft.Json; - -namespace Azaion.CommonSecurity.Services; - -public class AzaionApiClient(HttpClient httpClient) : IDisposable -{ - const string JSON_MEDIA = "application/json"; - - private static ApiConfig _apiConfig = null!; - - private string Email { get; set; } = null!; - private SecureString Password { get; set; } = new(); - private string JwtToken { get; set; } = null!; - public User User { get; set; } = null!; - - public static AzaionApiClient Create(ApiCredentials credentials, ApiConfig apiConfig) - { - _apiConfig = apiConfig; - var api = new AzaionApiClient(new HttpClient - { - BaseAddress = new Uri(_apiConfig.Url), - Timeout = TimeSpan.FromSeconds(_apiConfig.TimeoutSeconds) - }); - - api.EnterCredentials(credentials); - return api; - } - - public void EnterCredentials(ApiCredentials credentials) - { - if (string.IsNullOrWhiteSpace(credentials.Email) || string.IsNullOrWhiteSpace(credentials.Password)) - throw new Exception("Email or password is empty!"); - - Email = credentials.Email; - Password = credentials.Password.ToSecureString(); - } - - public async Task GetResource(string fileName, string password, HardwareInfo hardware, CancellationToken cancellationToken = default) - { - var response = await Send(httpClient, new HttpRequestMessage(HttpMethod.Post, $"/resources/get/{_apiConfig.ResourcesFolder}") - { - Content = new StringContent(JsonConvert.SerializeObject(new { fileName, password, hardware }), Encoding.UTF8, JSON_MEDIA) - }, cancellationToken); - return await response.Content.ReadAsStreamAsync(cancellationToken); - } - - private async Task Authorize() - { - if (string.IsNullOrEmpty(Email) || Password.Length == 0) - throw new Exception("Email or password is empty! Please do EnterCredentials first!"); - - var payload = new - { - email = Email, - password = Password.ToRealString() - }; - var response = await httpClient.PostAsync( - "login", - new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, JSON_MEDIA)); - - if (!response.IsSuccessStatusCode) - throw new Exception($"EnterCredentials failed: {response.StatusCode}"); - - var responseData = await response.Content.ReadAsStringAsync(); - - var result = JsonConvert.DeserializeObject(responseData); - - if (string.IsNullOrEmpty(result?.Token)) - throw new Exception("JWT Token not found in response"); - - var handler = new JwtSecurityTokenHandler(); - var token = handler.ReadJwtToken(result.Token); - - User = new User(token.Claims); - JwtToken = result.Token; - } - - private async Task Send(HttpClient client, HttpRequestMessage request, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(JwtToken)) - await Authorize(); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken); - var response = await client.SendAsync(request, cancellationToken); - - if (response.StatusCode == HttpStatusCode.Unauthorized) - { - await Authorize(); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken); - response = await client.SendAsync(request, cancellationToken); - } - - if (response.IsSuccessStatusCode) - return response; - - var result = await response.Content.ReadAsStringAsync(cancellationToken); - throw new Exception($"Failed: {response.StatusCode}! Result: {result}"); - } - - public void Dispose() - { - httpClient.Dispose(); - Password.Dispose(); - } -} \ No newline at end of file diff --git a/Azaion.CommonSecurity/Services/PythonResourceLoader.cs b/Azaion.CommonSecurity/Services/PythonResourceLoader.cs index c2dff3e..af799ee 100644 --- a/Azaion.CommonSecurity/Services/PythonResourceLoader.cs +++ b/Azaion.CommonSecurity/Services/PythonResourceLoader.cs @@ -19,7 +19,6 @@ public interface IAuthProvider User CurrentUser { get; } } - public class PythonResourceLoader : IResourceLoader, IAuthProvider { private readonly DealerSocket _dealer = new(); @@ -27,11 +26,11 @@ public class PythonResourceLoader : IResourceLoader, IAuthProvider public User CurrentUser { get; set; } = null!; - public PythonResourceLoader() + public PythonResourceLoader(PythonConfig config) { - StartPython(); + //StartPython(); _dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N")); - _dealer.Connect($"tcp://{SecurityConstants.ZMQ_HOST}:{SecurityConstants.ZMQ_PORT}"); + _dealer.Connect($"tcp://{config.ZeroMqHost}:{config.ZeroMqPort}"); } private void StartPython() diff --git a/Azaion.Inference/api_client.pxd b/Azaion.Inference/api_client.pxd index 4407551..4345a27 100644 --- a/Azaion.Inference/api_client.pxd +++ b/Azaion.Inference/api_client.pxd @@ -1,6 +1,6 @@ from user cimport User from credentials cimport Credentials -from file_data cimport FileData + cdef class ApiClient: cdef public Credentials credentials diff --git a/Azaion.Inference/build.cmd b/Azaion.Inference/build.cmd new file mode 100644 index 0000000..0707e36 --- /dev/null +++ b/Azaion.Inference/build.cmd @@ -0,0 +1,24 @@ +pyinstaller --onefile ^ +--collect-all pyyaml ^ +--collect-all jwt ^ +--collect-all requests ^ +--collect-all psutil ^ +--collect-all msgpack ^ +--collect-all zmq ^ +--collect-all cryptography ^ +--collect-all cv2 ^ +--collect-all onnxruntime ^ +--hidden-import constants ^ +--hidden-import annotation ^ +--hidden-import credentials ^ +--hidden-import file_data ^ +--hidden-import user ^ +--hidden-import security ^ +--hidden-import secure_model ^ +--hidden-import api_client ^ +--hidden-import hardware_service ^ +--hidden-import remote_command ^ +--hidden-import ai_config ^ +--hidden-import inference ^ +--hidden-import remote_command_handler ^ +start.py \ No newline at end of file diff --git a/Azaion.Inference/config.yaml b/Azaion.Inference/config.yaml new file mode 100644 index 0000000..f03cd9e --- /dev/null +++ b/Azaion.Inference/config.yaml @@ -0,0 +1 @@ +zmq_port: 5128 \ No newline at end of file diff --git a/Azaion.Inference/constants.pxd b/Azaion.Inference/constants.pxd index 77756b6..926d517 100644 --- a/Azaion.Inference/constants.pxd +++ b/Azaion.Inference/constants.pxd @@ -1,4 +1,4 @@ -cdef int ZMQ_PORT = 5127 # Port for the zmq +cdef str CONFIG_FILE # Port for the zmq cdef int QUEUE_MAXSIZE # Maximum size of the command queue cdef str COMMANDS_QUEUE # Name of the commands queue in rabbit diff --git a/Azaion.Inference/constants.pyx b/Azaion.Inference/constants.pyx index e1b85ea..5748e9f 100644 --- a/Azaion.Inference/constants.pyx +++ b/Azaion.Inference/constants.pyx @@ -1,6 +1,6 @@ -cdef int ZMQ_PORT = 5127 # Port for the zmq +cdef str CONFIG_FILE = "config.yaml" # Port for the zmq -cdef int QUEUE_MAXSIZE = 1000 # Maximum size of the command queue +cdef int QUEUE_MAXSIZE = 1000 # Maximum size of the command queue cdef str COMMANDS_QUEUE = "azaion-commands" cdef str ANNOTATIONS_QUEUE = "azaion-annotations" @@ -9,4 +9,4 @@ cdef str QUEUE_CONFIG_FILENAME = "secured-config.json" cdef str AI_MODEL_FILE = "azaion.onnx" cdef bytes DONE_SIGNAL = b"DONE" -cdef int MODEL_BATCH_SIZE = 4 \ No newline at end of file +cdef int MODEL_BATCH_SIZE = 4 diff --git a/Azaion.Inference/remote_command_handler.pyx b/Azaion.Inference/remote_command_handler.pyx index 383ece9..b44b051 100644 --- a/Azaion.Inference/remote_command_handler.pyx +++ b/Azaion.Inference/remote_command_handler.pyx @@ -3,7 +3,7 @@ import zmq from threading import Thread, Event from remote_command cimport RemoteCommand cimport constants - +import yaml cdef class RemoteCommandHandler: def __init__(self, object on_command): @@ -12,7 +12,9 @@ cdef class RemoteCommandHandler: self._router = self._context.socket(zmq.ROUTER) self._router.setsockopt(zmq.LINGER, 0) - self._router.bind(f'tcp://*:{constants.ZMQ_PORT}') + with open(constants.CONFIG_FILE, "r") as f: + config = yaml.safe_load(f) + self._router.bind(f'tcp://*:{config["zmq_port"]}') self._dealer = self._context.socket(zmq.DEALER) self._dealer.setsockopt(zmq.LINGER, 0) diff --git a/Azaion.Suite/App.xaml.cs b/Azaion.Suite/App.xaml.cs index 912ed97..ea1f2c8 100644 --- a/Azaion.Suite/App.xaml.cs +++ b/Azaion.Suite/App.xaml.cs @@ -33,7 +33,7 @@ public partial class App private IMediator _mediator = null!; private FormState _formState = null!; - private readonly PythonResourceLoader _resourceLoader = new(); + private PythonResourceLoader _resourceLoader = null!; private Stream _securedConfig = null!; private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) @@ -54,23 +54,25 @@ public partial class App "Azaion.Dataset" ]; - private static ApiConfig ReadConfig() + private static PythonConfig ReadPythonConfig() { try { if (!File.Exists(SecurityConstants.CONFIG_PATH)) throw new FileNotFoundException(SecurityConstants.CONFIG_PATH); var configStr = File.ReadAllText(SecurityConstants.CONFIG_PATH); - return JsonConvert.DeserializeObject(configStr)!.ApiConfig; + return JsonConvert.DeserializeObject(configStr)!.PythonConfig; } catch (Exception e) { Console.WriteLine(e); - return new ApiConfig + return new PythonConfig { - Url = SecurityConstants.DEFAULT_API_URL, - RetryCount = SecurityConstants.DEFAULT_API_RETRY_COUNT, - TimeoutSeconds = SecurityConstants.DEFAULT_API_TIMEOUT_SECONDS, + ZeroMqHost = SecurityConstants.DEFAULT_ZMQ_HOST, + ZeroMqPort = SecurityConstants.DEFAULT_ZMQ_PORT, + OneTryTimeoutSeconds = SecurityConstants.DEFAULT_TIMEOUT_SECONDS, + RetryCount = SecurityConstants.DEFAULT_RETRY_COUNT, + ResourcesFolder = "" }; } @@ -80,9 +82,10 @@ public partial class App { new ConfigUpdater().CheckConfig(); var login = new Login(); + var config = ReadPythonConfig(); + _resourceLoader = new PythonResourceLoader(config); login.CredentialsEntered += (_, credentials) => { - var config = ReadConfig(); credentials.Folder = config.ResourcesFolder; _resourceLoader.Login(credentials); _securedConfig = _resourceLoader.LoadFileFromPython("secured-config.json"); diff --git a/Azaion.Suite/config.json b/Azaion.Suite/config.json index 2e9e391..fb8697c 100644 --- a/Azaion.Suite/config.json +++ b/Azaion.Suite/config.json @@ -1,6 +1,9 @@ { - "ApiConfig": { - "TimeoutSeconds": 20.0 + "PythonConfig": { + "ZeroMqHost": "127.0.0.1", + "ZeroMqPort": 5128, + "RetryCount": 25, + "TimeoutSeconds": 5 }, "DirectoriesConfig": { "VideosDirectory": "E:\\Azaion6",