diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d3eca3dd..b34c26092 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -421,19 +421,28 @@ jobs: strategy: fail-fast: false matrix: - queue: - - activemq - - rabbitmq - - rabbitmq091 - - pubsub - - sqs - object: - - redis - - minio - log-level: - - Information - - Verbose - name: HtcMock ${{ matrix.queue }} ${{ matrix.object }} ${{ matrix.log-level }} + target: + - { queue: activemq, object: redis, log-level: Information, cinit: true } + - { queue: rabbitmq, object: redis, log-level: Information, cinit: true } + - { queue: rabbitmq091, object: redis, log-level: Information, cinit: true } + - { queue: pubsub, object: redis, log-level: Information, cinit: true } + - { queue: sqs, object: redis, log-level: Information, cinit: true } + + - { queue: activemq, object: redis, log-level: Verbose, cinit: true } + - { queue: rabbitmq, object: redis, log-level: Verbose, cinit: true } + - { queue: rabbitmq091, object: redis, log-level: Verbose, cinit: true } + - { queue: pubsub, object: redis, log-level: Verbose, cinit: true } + - { queue: sqs, object: redis, log-level: Verbose, cinit: true } + + - { queue: activemq, object: minio, log-level: Information, cinit: true } + - { queue: rabbitmq, object: minio, log-level: Information, cinit: true } + - { queue: rabbitmq091, object: minio, log-level: Information, cinit: true } + - { queue: pubsub, object: minio, log-level: Information, cinit: true } + - { queue: sqs, object: minio, log-level: Information, cinit: true } + + - { queue: activemq, object: redis, log-level: Information, cinit: false } + + name: HtcMock ${{ matrix.target.queue }} ${{ matrix.target.object }} ${{ matrix.target.log-level }} ${{ matrix.target.cinit }} steps: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 @@ -455,7 +464,7 @@ jobs: - name: Deploy Core run: | MONITOR_PREFIX="monitor/deploy/" tools/retry.sh -w 30 -- tools/monitor.sh \ - just log_level=${{ matrix.log-level }} tag=${VERSION} queue=${{ matrix.queue }} object=${{ matrix.object }} worker=htcmock deploy + just log_level=${{ matrix.target.log-level }} tag=${VERSION} queue=${{ matrix.target.queue }} object=${{ matrix.target.object }} cinit=${{ matrix.target.cinit }} worker=htcmock deploy sleep 10 - name: Print And Time Metrics @@ -524,7 +533,7 @@ jobs: - name: Run HtcMock test 1000 tasks 1 level timeout-minutes: 3 - if: ${{ matrix.log-level != 'Verbose' }} + if: ${{ matrix.target.log-level != 'Verbose' }} run: | MONITOR_PREFIX="monitor/htcmock-1000-1/" tools/monitor.sh \ docker run --net armonik_network --rm \ @@ -539,7 +548,7 @@ jobs: - name: Run HtcMock test 1000 tasks 4 levels timeout-minutes: 3 - if: ${{ matrix.log-level != 'Verbose' }} + if: ${{ matrix.target.log-level != 'Verbose' }} run: | MONITOR_PREFIX="monitor/htcmock-1000-4/" tools/monitor.sh \ docker run --net armonik_network --rm \ @@ -563,8 +572,8 @@ jobs: run: | export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} - docker cp fluentd:/armonik-logs.json - | gzip -c | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}.json.gz - tar -czf - monitor/ | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}-monitor.tar.gz + docker cp fluentd:/armonik-logs.json - | gzip -c | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}.json.gz + tar -czf - monitor/ | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}-monitor.tar.gz - name: Collect docker container logs uses: jwalton/gh-docker-logs@2741064ab9d7af54b0b1ffb6076cf64c16f0220e # v2 @@ -576,14 +585,14 @@ jobs: run: | export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} - tar -cvf - ./container-logs | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}-container-logs.tar.gz + tar -cvf - ./container-logs | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}-container-logs.tar.gz - name: Export and upload database if: always() run: | export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} bash tools/export_mongodb.sh - tar -cvf - *.json | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}-database.tar.gz + tar -cvf - *.json | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}-database.tar.gz testWindowsDocker: needs: diff --git a/Adaptors/MongoDB/src/Common/IMongoDataModelMapping.cs b/Adaptors/MongoDB/src/Common/IMongoDataModelMapping.cs index 7a3399a48..74bf7f6e3 100644 --- a/Adaptors/MongoDB/src/Common/IMongoDataModelMapping.cs +++ b/Adaptors/MongoDB/src/Common/IMongoDataModelMapping.cs @@ -17,6 +17,8 @@ using System.Threading.Tasks; +using ArmoniK.Core.Common.Injection.Options.Database; + using MongoDB.Driver; namespace ArmoniK.Core.Adapters.MongoDB.Common; @@ -25,10 +27,44 @@ public interface IMongoDataModelMapping { string CollectionName { get; } + /// + /// Setup indexes for the collection + /// Can be called multiple times + /// + /// MongoDB Client session + /// MongoDDB Collection in which to insert data + /// Options for MongoDB + /// + /// Task representing the asynchronous execution of the method + /// Task InitializeIndexesAsync(IClientSessionHandle sessionHandle, IMongoCollection collection, Options.MongoDB options); + /// + /// Setup sharding for the collection + /// Can be called multiple times + /// + /// MongoDB Client session + /// Options for MongoDB + /// + /// Task representing the asynchronous execution of the method + /// Task ShardCollectionAsync(IClientSessionHandle sessionHandle, Options.MongoDB options); + + /// + /// Insert data into the collection after its creation. + /// Can be called multiple times + /// + /// MongoDB Client session + /// MongoDDB Collection in which to insert data + /// Data to insert + /// + /// Task representing the asynchronous execution of the method + /// + Task InitializeCollectionAsync(IClientSessionHandle sessionHandle, + IMongoCollection collection, + InitDatabase initDatabase) + => Task.CompletedTask; } diff --git a/Adaptors/MongoDB/src/Common/MongoCollectionProvider.cs b/Adaptors/MongoDB/src/Common/MongoCollectionProvider.cs index 578bcdffa..c37cfdd59 100644 --- a/Adaptors/MongoDB/src/Common/MongoCollectionProvider.cs +++ b/Adaptors/MongoDB/src/Common/MongoCollectionProvider.cs @@ -16,12 +16,14 @@ // along with this program. If not, see . using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using ArmoniK.Core.Base; using ArmoniK.Core.Base.DataStructures; using ArmoniK.Core.Common; +using ArmoniK.Core.Common.Injection.Options.Database; using JetBrains.Annotations; @@ -40,6 +42,7 @@ public class MongoCollectionProvider : IInitializable, IAs private IMongoCollection? mongoCollection_; public MongoCollectionProvider(Options.MongoDB options, + InitDatabase initDatabase, SessionProvider sessionProvider, IMongoDatabase mongoDatabase, ILogger> logger, @@ -52,6 +55,7 @@ public MongoCollectionProvider(Options.MongoDB options, } Initialization = InitializeAsync(options, + initDatabase, sessionProvider, mongoDatabase, logger, @@ -88,6 +92,7 @@ public async Task Init(CancellationToken cancellationToken) } private static async Task> InitializeAsync(Options.MongoDB options, + InitDatabase initDatabase, SessionProvider sessionProvider, IMongoDatabase mongoDatabase, ILogger> logger, @@ -96,47 +101,55 @@ private static async Task> InitializeAsync(Options.Mongo var model = new TModelMapping(); Exception? lastException = null; - for (var collectionRetry = 1; collectionRetry < options.MaxRetries; collectionRetry++) + if (initDatabase.Init) { - lastException = null; - try - { - await mongoDatabase.CreateCollectionAsync(model.CollectionName, - null, - cancellationToken) - .ConfigureAwait(false); - break; - } - catch (MongoCommandException ex) when (ex.CodeName == "NamespaceExists") + for (var collectionRetry = 1; collectionRetry < options.MaxRetries; collectionRetry++) { - logger.LogDebug(ex, - "Use already existing instance of Collection {CollectionName}", - model.CollectionName); - break; + lastException = null; + try + { + await mongoDatabase.CreateCollectionAsync(model.CollectionName, + null, + cancellationToken) + .ConfigureAwait(false); + break; + } + catch (MongoCommandException ex) when (ex.CodeName == "NamespaceExists") + { + logger.LogDebug(ex, + "Use already existing instance of Collection {CollectionName}", + model.CollectionName); + break; + } + catch (Exception ex) + { + lastException = ex; + logger.LogDebug(ex, + "Retrying to create Collection {CollectionName}", + model.CollectionName); + await Task.Delay(1000 * collectionRetry, + cancellationToken) + .ConfigureAwait(false); + } } - catch (Exception ex) + + if (lastException is not null) { - lastException = ex; - logger.LogDebug(ex, - "Retrying to create Collection {CollectionName}", - model.CollectionName); - await Task.Delay(1000 * collectionRetry, - cancellationToken) - .ConfigureAwait(false); + throw new TimeoutException($"Create {model.CollectionName}: Max retries reached", + lastException); } } - if (lastException is not null) - { - throw new TimeoutException($"Create {model.CollectionName}: Max retries reached", - lastException); - } - var output = mongoDatabase.GetCollection(model.CollectionName); await sessionProvider.Init(cancellationToken) .ConfigureAwait(false); var session = sessionProvider.Get(); + if (!initDatabase.Init) + { + return output; + } + for (var indexRetry = 1; indexRetry < options.MaxRetries; indexRetry++) { lastException = null; @@ -192,9 +205,39 @@ await Task.Delay(1000 * indexRetry, } } + for (var indexRetry = 1; indexRetry < options.MaxRetries; indexRetry++) + { + lastException = null; + try + { + await model.InitializeCollectionAsync(session, + output, + initDatabase) + .ConfigureAwait(false); + break; + } + catch (MongoBulkWriteException e) when (e.WriteErrors.All(error => error.Category == ServerErrorCategory.DuplicateKey)) + { + logger.LogDebug(e, + "Values were already present within the collection {CollectionName}", + model.CollectionName); + break; + } + catch (Exception ex) + { + lastException = ex; + logger.LogDebug(ex, + "Retrying to initialize {CollectionName} collection", + model.CollectionName); + await Task.Delay(1000 * indexRetry, + cancellationToken) + .ConfigureAwait(false); + } + } + if (lastException is not null) { - throw new TimeoutException($"Init Index or shard for {model.CollectionName}: Max retries reached", + throw new TimeoutException($"Init for {model.CollectionName}: Max retries reached", lastException); } diff --git a/Adaptors/MongoDB/src/Table/DataModel/Auth/AuthDataModelMapping.cs b/Adaptors/MongoDB/src/Table/DataModel/Auth/AuthDataModelMapping.cs index bae6c44a5..856774458 100644 --- a/Adaptors/MongoDB/src/Table/DataModel/Auth/AuthDataModelMapping.cs +++ b/Adaptors/MongoDB/src/Table/DataModel/Auth/AuthDataModelMapping.cs @@ -15,10 +15,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.Linq; using System.Threading.Tasks; using ArmoniK.Core.Adapters.MongoDB.Common; using ArmoniK.Core.Common.Auth.Authentication; +using ArmoniK.Core.Common.Injection.Options.Database; using MongoDB.Bson; using MongoDB.Bson.Serialization; @@ -78,7 +80,21 @@ await collection.Indexes.CreateManyAsync(sessionHandle, .ConfigureAwait(false); } + /// public Task ShardCollectionAsync(IClientSessionHandle sessionHandle, Options.MongoDB options) => Task.CompletedTask; + + /// + public async Task InitializeCollectionAsync(IClientSessionHandle sessionHandle, + IMongoCollection collection, + InitDatabase initDatabase) + { + if (initDatabase.Auths.Any()) + { + await collection.InsertManyAsync(sessionHandle, + initDatabase.Auths) + .ConfigureAwait(false); + } + } } diff --git a/Adaptors/MongoDB/src/Table/DataModel/Auth/RoleDataModelMapping.cs b/Adaptors/MongoDB/src/Table/DataModel/Auth/RoleDataModelMapping.cs index 5da24a1cc..927715ff9 100644 --- a/Adaptors/MongoDB/src/Table/DataModel/Auth/RoleDataModelMapping.cs +++ b/Adaptors/MongoDB/src/Table/DataModel/Auth/RoleDataModelMapping.cs @@ -16,10 +16,12 @@ // along with this program. If not, see . using System; +using System.Linq; using System.Threading.Tasks; using ArmoniK.Core.Adapters.MongoDB.Common; using ArmoniK.Core.Common.Auth.Authentication; +using ArmoniK.Core.Common.Injection.Options.Database; using MongoDB.Bson.Serialization; using MongoDB.Driver; @@ -74,7 +76,21 @@ await collection.Indexes.CreateManyAsync(sessionHandle, .ConfigureAwait(false); } + /// public Task ShardCollectionAsync(IClientSessionHandle sessionHandle, Options.MongoDB options) => Task.CompletedTask; + + /// + public async Task InitializeCollectionAsync(IClientSessionHandle sessionHandle, + IMongoCollection collection, + InitDatabase initDatabase) + { + if (initDatabase.Roles.Any()) + { + await collection.InsertManyAsync(sessionHandle, + initDatabase.Roles) + .ConfigureAwait(false); + } + } } diff --git a/Adaptors/MongoDB/src/Table/DataModel/Auth/UserDataModelMapping.cs b/Adaptors/MongoDB/src/Table/DataModel/Auth/UserDataModelMapping.cs index 56d22fcbd..048647c8a 100644 --- a/Adaptors/MongoDB/src/Table/DataModel/Auth/UserDataModelMapping.cs +++ b/Adaptors/MongoDB/src/Table/DataModel/Auth/UserDataModelMapping.cs @@ -16,10 +16,12 @@ // along with this program. If not, see . using System; +using System.Linq; using System.Threading.Tasks; using ArmoniK.Core.Adapters.MongoDB.Common; using ArmoniK.Core.Common.Auth.Authentication; +using ArmoniK.Core.Common.Injection.Options.Database; using MongoDB.Bson.Serialization; using MongoDB.Driver; @@ -74,7 +76,21 @@ await collection.Indexes.CreateManyAsync(sessionHandle, .ConfigureAwait(false); } + /// public Task ShardCollectionAsync(IClientSessionHandle sessionHandle, Options.MongoDB options) => Task.CompletedTask; + + /// + public async Task InitializeCollectionAsync(IClientSessionHandle sessionHandle, + IMongoCollection collection, + InitDatabase initDatabase) + { + if (initDatabase.Users.Any()) + { + await collection.InsertManyAsync(sessionHandle, + initDatabase.Users) + .ConfigureAwait(false); + } + } } diff --git a/Adaptors/MongoDB/src/Table/DataModel/PartitionDataModelMapping.cs b/Adaptors/MongoDB/src/Table/DataModel/PartitionDataModelMapping.cs index 2c22bc412..8cb8b4fa3 100644 --- a/Adaptors/MongoDB/src/Table/DataModel/PartitionDataModelMapping.cs +++ b/Adaptors/MongoDB/src/Table/DataModel/PartitionDataModelMapping.cs @@ -15,9 +15,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.Linq; using System.Threading.Tasks; using ArmoniK.Core.Adapters.MongoDB.Common; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; using MongoDB.Bson.Serialization; @@ -91,7 +93,21 @@ await collection.Indexes.CreateManyAsync(sessionHandle, .ConfigureAwait(false); } + /// public Task ShardCollectionAsync(IClientSessionHandle sessionHandle, Options.MongoDB options) => Task.CompletedTask; + + /// + public async Task InitializeCollectionAsync(IClientSessionHandle sessionHandle, + IMongoCollection collection, + InitDatabase initDatabase) + { + if (initDatabase.Partitions.Any()) + { + await collection.InsertManyAsync(sessionHandle, + initDatabase.Partitions) + .ConfigureAwait(false); + } + } } diff --git a/Adaptors/MongoDB/tests/IndexTest.cs b/Adaptors/MongoDB/tests/IndexTest.cs index 9a44df275..24b6c127d 100644 --- a/Adaptors/MongoDB/tests/IndexTest.cs +++ b/Adaptors/MongoDB/tests/IndexTest.cs @@ -21,7 +21,10 @@ using ArmoniK.Core.Adapters.MongoDB.Table.DataModel; using ArmoniK.Core.Common.Auth.Authentication; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; +using ArmoniK.Core.Utils; using EphemeralMongo; @@ -77,6 +80,9 @@ public void StartUp() logger); services.AddSingleton(ActivitySource); services.AddTransient(_ => client_); + services.AddInitializedOption(configuration, + InitServices.SettingSection); + services.AddSingleton(); services.AddLogging(); provider_ = services.BuildServiceProvider(new ServiceProviderOptions diff --git a/Adaptors/MongoDB/tests/InjectionTests.cs b/Adaptors/MongoDB/tests/InjectionTests.cs index d46387388..ab8fc22b5 100644 --- a/Adaptors/MongoDB/tests/InjectionTests.cs +++ b/Adaptors/MongoDB/tests/InjectionTests.cs @@ -20,6 +20,8 @@ using System.Diagnostics; using ArmoniK.Core.Adapters.MongoDB.Options; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; using ArmoniK.Core.Utils; @@ -100,6 +102,9 @@ public void SetUp() var services = new ServiceCollection(); services.AddMongoComponents(configuration_, logger); + services.AddInitializedOption(configuration_, + InitServices.SettingSection); + services.AddSingleton(); services.AddSingleton(ActivitySource); services.AddLogging(); provider_ = services.BuildServiceProvider(new ServiceProviderOptions diff --git a/Adaptors/MongoDB/tests/MongoDatabaseProvider.cs b/Adaptors/MongoDB/tests/MongoDatabaseProvider.cs index 3d6232496..2ae12b790 100644 --- a/Adaptors/MongoDB/tests/MongoDatabaseProvider.cs +++ b/Adaptors/MongoDB/tests/MongoDatabaseProvider.cs @@ -20,6 +20,8 @@ using System.Diagnostics; using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; +using ArmoniK.Core.Utils; using EphemeralMongo; @@ -103,7 +105,8 @@ public MongoDatabaseProvider(bool useSingleNodeReplicaSet }; var configuration = new ConfigurationManager(); - configuration.AddInMemoryCollection(minimalConfig); + configuration.AddInMemoryCollection(minimalConfig) + .AddEnvironmentVariables(); var serviceCollection = new ServiceCollection(); serviceCollection.AddMongoStorages(configuration, @@ -111,6 +114,9 @@ public MongoDatabaseProvider(bool useSingleNodeReplicaSet serviceCollection.AddClientSubmitterAuthenticationStorage(configuration); serviceCollection.AddSingleton(ActivitySource); serviceCollection.AddTransient(_ => client); + serviceCollection.AddInitializedOption(configuration, + InitServices.SettingSection); + serviceCollection.AddSingleton(); serviceCollection.AddLogging(builder => builder.AddSerilog(loggerSerilog)); serviceConfigurator?.Invoke(serviceCollection); diff --git a/Adaptors/MongoDB/tests/SessionProviderTests.cs b/Adaptors/MongoDB/tests/SessionProviderTests.cs index e4e7b7743..2889f11a7 100644 --- a/Adaptors/MongoDB/tests/SessionProviderTests.cs +++ b/Adaptors/MongoDB/tests/SessionProviderTests.cs @@ -23,6 +23,9 @@ using ArmoniK.Core.Adapters.MongoDB.Common; using ArmoniK.Core.Base.DataStructures; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; +using ArmoniK.Core.Utils; using EphemeralMongo; @@ -88,6 +91,9 @@ public void SetUp() logger); services.AddSingleton(ActivitySource); services.AddTransient(_ => client_); + services.AddInitializedOption(configuration, + InitServices.SettingSection); + services.AddSingleton(); services.AddLogging(); provider_ = services.BuildServiceProvider(new ServiceProviderOptions diff --git a/Common/src/Injection/Options/Database/Authentication.cs b/Common/src/Injection/Options/Database/Authentication.cs new file mode 100644 index 000000000..07d8a254a --- /dev/null +++ b/Common/src/Injection/Options/Database/Authentication.cs @@ -0,0 +1,46 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Collections.Generic; + +namespace ArmoniK.Core.Common.Injection.Options.Database; + +/// +/// Options fill authentication related data +/// +public record Authentication +{ + /// + /// Path to the section containing the values in the configuration object + /// + public const string SettingSection = nameof(Authentication); + + /// + /// User certificates used for authentication in a JSON string + /// + public List UserCertificates { get; init; } = new(); + + /// + /// Roles used for authentication in a JSON string + /// + public List Roles { get; init; } = new(); + + /// + /// Users used for authentication in a JSON string + /// + public List Users { get; init; } = new(); +} diff --git a/Common/src/Injection/Options/Database/Certificate.cs b/Common/src/Injection/Options/Database/Certificate.cs new file mode 100644 index 000000000..a8c820aa2 --- /dev/null +++ b/Common/src/Injection/Options/Database/Certificate.cs @@ -0,0 +1,61 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Text.Json; + +namespace ArmoniK.Core.Common.Injection.Options.Database; + +/// +/// Placeholder to deserialize certificates for Authentication provided by users +/// +public record Certificate +{ + /// + /// User associated to certificate + /// + public required string User { get; init; } + + // ReSharper disable once InconsistentNaming + /// + /// Certificate common name + /// + public required string CN { get; init; } + + /// + /// Certificate fingerprint + /// + public required string? Fingerprint { get; init; } + + /// + /// Convert to JSON + /// + /// + /// representing the JSON object of this instance + /// + public string ToJson() + => JsonSerializer.Serialize(this); + + /// + /// Build a from a JSON + /// + /// JSON value + /// + /// built from the provided JSON + /// + public static Certificate FromJson(string json) + => JsonSerializer.Deserialize(json)!; +} diff --git a/Common/src/Injection/Options/Database/InitDatabase.cs b/Common/src/Injection/Options/Database/InitDatabase.cs new file mode 100644 index 000000000..df814902f --- /dev/null +++ b/Common/src/Injection/Options/Database/InitDatabase.cs @@ -0,0 +1,105 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Collections.Generic; +using System.Linq; + +using ArmoniK.Core.Common.Auth.Authentication; +using ArmoniK.Core.Common.Storage; +using ArmoniK.Utils; + +namespace ArmoniK.Core.Common.Injection.Options.Database; + +/// +/// Convert into objects we can insert into the database +/// Also used to init database collections +/// +public class InitDatabase +{ + /// + /// Collection of data to insert in the database for Authentication during ArmoniK initialization + /// + public readonly ICollection Auths; + + /// + /// Whether to init the database + /// + public readonly bool Init; + + /// + /// Collection of data to insert in the database for Partitions during ArmoniK initialization + /// + public readonly ICollection Partitions; + + /// + /// Collection of data to insert in the database for Roles during ArmoniK initialization + /// + public readonly ICollection Roles; + + /// + /// Collection of data to insert in the database for Users during ArmoniK initialization + /// + public readonly ICollection Users; + + + /// + /// Instantiate from the configurations received from the Dependency Injection + /// + /// Data structure containing the raw data + public InitDatabase(InitServices initServices) + { + Init = initServices.InitDatabase; + + Roles = initServices.Authentication.Roles.Select(Role.FromJson) + .OrderBy(role => role.Name) + .Select((role, + i) => new RoleData(i.ToString(), + role.Name, + role.Permissions.ToArray())) + .AsICollection(); + + Users = initServices.Authentication.Users.Select(User.FromJson) + .OrderBy(user => user.Name) + .Select((user, + i) => new UserData(i.ToString(), + user.Name, + user.Roles.ToArray())) + .AsICollection(); + + var userDic = Users.ToDictionary(data => data.Username, + data => data.UserId); + + Auths = initServices.Authentication.UserCertificates.Select(Certificate.FromJson) + .OrderBy(certificate => (certificate.Fingerprint, certificate.CN)) + .Select((certificate, + i) => new AuthData(i.ToString(), + userDic[certificate.User], + certificate.CN, + certificate.Fingerprint)) + .AsICollection(); + + Partitions = initServices.Partitioning.Partitions.Select(Partition.FromJson) + .Select(partition => new PartitionData(partition.PartitionId, + partition.ParentPartitionIds, + partition.PodReserved, + partition.PodMax, + partition.PreemptionPercentage, + partition.Priority, + new PodConfiguration(partition.PodConfiguration))) + .AsICollection(); + } +} diff --git a/Common/src/Injection/Options/Database/Partition.cs b/Common/src/Injection/Options/Database/Partition.cs new file mode 100644 index 000000000..0b8e5cd35 --- /dev/null +++ b/Common/src/Injection/Options/Database/Partition.cs @@ -0,0 +1,86 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +using ArmoniK.Core.Common.Storage; + +namespace ArmoniK.Core.Common.Injection.Options.Database; + +/// +public record Partition +{ + /// + public required string PartitionId { get; init; } + + /// + public required IList ParentPartitionIds { get; init; } + + /// + public required int PodReserved { get; init; } + + /// + public required int PodMax { get; init; } + + /// + public required int PreemptionPercentage { get; init; } + + /// + public required int Priority { get; init; } + + /// + public IDictionary PodConfiguration { get; init; } = new Dictionary(); + + /// + public virtual bool Equals(Partition? other) + => !ReferenceEquals(other, + null) && PartitionId.Equals(other.PartitionId) && ParentPartitionIds.SequenceEqual(other.ParentPartitionIds) && + PodReserved.Equals(other.PodReserved) && PodMax.Equals(other.PodMax) && PreemptionPercentage.Equals(other.PreemptionPercentage) && + Priority.Equals(other.Priority) && PodConfiguration.SequenceEqual(other.PodConfiguration); + + /// + /// Convert to JSON + /// + /// + /// representing the JSON object of this instance + /// + public string ToJson() + => JsonSerializer.Serialize(this); + + /// + /// Build a from a JSON + /// + /// JSON value + /// + /// built from the provided JSON + /// + public static Partition FromJson(string json) + => JsonSerializer.Deserialize(json)!; + + /// + public override int GetHashCode() + => HashCode.Combine(PartitionId, + ParentPartitionIds, + PodReserved, + PodMax, + PreemptionPercentage, + Priority, + PodConfiguration); +} diff --git a/Common/src/Injection/Options/Database/Partitioning.cs b/Common/src/Injection/Options/Database/Partitioning.cs new file mode 100644 index 000000000..f21bd6cd4 --- /dev/null +++ b/Common/src/Injection/Options/Database/Partitioning.cs @@ -0,0 +1,36 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Collections.Generic; + +namespace ArmoniK.Core.Common.Injection.Options.Database; + +/// +/// Options to fill partitions related data +/// +public class Partitioning +{ + /// + /// Path to the section containing the values in the configuration object + /// + public const string SettingSection = nameof(Partitioning); + + /// + /// Partitions in a JSON string to insert into the database + /// + public List Partitions { get; init; } = new(); +} diff --git a/Common/src/Injection/Options/Database/Role.cs b/Common/src/Injection/Options/Database/Role.cs new file mode 100644 index 000000000..c2c53eb4e --- /dev/null +++ b/Common/src/Injection/Options/Database/Role.cs @@ -0,0 +1,68 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace ArmoniK.Core.Common.Injection.Options.Database; + +/// +/// Associate a Role to the permission it has +/// +public record Role +{ + /// + /// Role Name + /// + public required string Name { get; init; } + + /// + /// Permissions associated to the Role + /// + public required List Permissions { get; init; } + + /// + public virtual bool Equals(Role? other) + => !ReferenceEquals(null, + other) && Name.Equals(other.Name) && Permissions.SequenceEqual(other.Permissions); + + /// + /// Convert to JSON + /// + /// + /// representing the JSON object of this instance + /// + public string ToJson() + => JsonSerializer.Serialize(this); + + /// + /// Build a from a JSON + /// + /// JSON value + /// + /// built from the provided JSON + /// + public static Role FromJson(string json) + => JsonSerializer.Deserialize(json)!; + + /// + public override int GetHashCode() + => HashCode.Combine(Name, + Permissions); +} diff --git a/Common/src/Injection/Options/Database/User.cs b/Common/src/Injection/Options/Database/User.cs new file mode 100644 index 000000000..fed8c212e --- /dev/null +++ b/Common/src/Injection/Options/Database/User.cs @@ -0,0 +1,68 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace ArmoniK.Core.Common.Injection.Options.Database; + +/// +/// Associate a User to its Roles +/// +public record User +{ + /// + /// User Name + /// + public required string Name { get; init; } + + /// + /// Roles associated to the user + /// + public required List Roles { get; init; } + + /// + public virtual bool Equals(User? other) + => !ReferenceEquals(null, + other) && Name.Equals(other.Name) && Roles.SequenceEqual(other.Roles); + + /// + /// Convert to JSON + /// + /// + /// representing the JSON object of this instance + /// + public string ToJson() + => JsonSerializer.Serialize(this); + + /// + /// Build a from a JSON + /// + /// JSON value + /// + /// built from the provided JSON + /// + public static User FromJson(string json) + => JsonSerializer.Deserialize(json)!; + + /// + public override int GetHashCode() + => HashCode.Combine(Name, + Roles); +} diff --git a/Common/src/Injection/Options/InitServices.cs b/Common/src/Injection/Options/InitServices.cs new file mode 100644 index 000000000..52c8f9acf --- /dev/null +++ b/Common/src/Injection/Options/InitServices.cs @@ -0,0 +1,62 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using ArmoniK.Core.Common.Injection.Options.Database; + +namespace ArmoniK.Core.Common.Injection.Options; + +/// +/// Configuration for ArmoniK services +/// +public class InitServices +{ + /// + /// Path to the section containing the values in the configuration object + /// + public const string SettingSection = nameof(InitServices); + + /// + /// Authentication configurations + /// + public Authentication Authentication { get; set; } = new(); + + /// + /// Partitioning configurations + /// + public Partitioning Partitioning { get; set; } = new(); + + /// + /// Whether to perform database initialization (collection creation, indexing, sharding, data insertion, etc...). + /// + public bool InitDatabase { get; set; } = true; + + + /// + /// Whether to perform object storage initialization + /// + public bool InitObjectStorage { get; set; } = true; + + /// + /// Whether to perform queue initialization + /// + public bool InitQueue { get; set; } = true; + + /// + /// Stop the service after performing initialization + /// + public bool StopAfterInit { get; set; } = false; +} diff --git a/Common/src/Storage/PartitionData.cs b/Common/src/Storage/PartitionData.cs index 7053bdc6f..d8497ab6d 100644 --- a/Common/src/Storage/PartitionData.cs +++ b/Common/src/Storage/PartitionData.cs @@ -28,7 +28,7 @@ namespace ArmoniK.Core.Common.Storage; /// Max number of pods /// Percentage of pods that can be preempted /// Priority of the partition -/// Pod configuration used to select machines +/// Configuration for compute plane instances in this Partition public record PartitionData(string PartitionId, IList ParentPartitionIds, int PodReserved, diff --git a/Common/tests/Helpers/TestDatabaseProvider.cs b/Common/tests/Helpers/TestDatabaseProvider.cs index 388fe18c0..2b0aeaab9 100644 --- a/Common/tests/Helpers/TestDatabaseProvider.cs +++ b/Common/tests/Helpers/TestDatabaseProvider.cs @@ -25,7 +25,10 @@ using ArmoniK.Core.Adapters.MongoDB.Common; using ArmoniK.Core.Common.Auth.Authentication; using ArmoniK.Core.Common.Injection; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; +using ArmoniK.Core.Utils; using EphemeralMongo; @@ -138,6 +141,9 @@ public TestDatabaseProvider(Action? collectionConfigurato .AddClientSubmitterAuthenticationStorage(builder.Configuration) .AddClientSubmitterAuthServices(builder.Configuration, out _) + .AddInitializedOption(builder.Configuration, + InitServices.SettingSection) + .AddSingleton() .Configure(o => o.CopyFrom(AuthenticatorOptions.DefaultNoAuth)) .AddLogging() .AddSingleton(loggerProvider.CreateLogger("root")) diff --git a/Common/tests/Helpers/TestPollsterProvider.cs b/Common/tests/Helpers/TestPollsterProvider.cs index 41ad30a17..b08cc6cf1 100644 --- a/Common/tests/Helpers/TestPollsterProvider.cs +++ b/Common/tests/Helpers/TestPollsterProvider.cs @@ -28,6 +28,8 @@ using ArmoniK.Core.Adapters.MongoDB; using ArmoniK.Core.Base; using ArmoniK.Core.Common.gRPC.Services; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Meter; using ArmoniK.Core.Common.Pollster; using ArmoniK.Core.Common.Pollster.TaskProcessingChecker; @@ -168,6 +170,9 @@ public TestPollsterProvider(IWorkerStreamHandler workerStreamHandler, .AddSingleton() .AddOption(builder.Configuration, Injection.Options.Pollster.SettingSection) + .AddInitializedOption(builder.Configuration, + InitServices.SettingSection) + .AddSingleton() .AddSingleton(sp => new ExceptionManager.Options(sp.GetRequiredService() .GraceDelay, sp.GetRequiredService() diff --git a/Common/tests/Helpers/TestTaskHandlerProvider.cs b/Common/tests/Helpers/TestTaskHandlerProvider.cs index ea4dc6064..2786d0e9d 100644 --- a/Common/tests/Helpers/TestTaskHandlerProvider.cs +++ b/Common/tests/Helpers/TestTaskHandlerProvider.cs @@ -25,6 +25,8 @@ using ArmoniK.Core.Adapters.MongoDB; using ArmoniK.Core.Base; using ArmoniK.Core.Common.gRPC.Services; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Meter; using ArmoniK.Core.Common.Pollster; using ArmoniK.Core.Common.Pollster.TaskProcessingChecker; @@ -158,6 +160,9 @@ public TestTaskHandlerProvider(IWorkerStreamHandler workerStreamHandler, Injection.Options.Submitter.SettingSection) .AddOption(builder.Configuration, Injection.Options.Pollster.SettingSection) + .AddInitializedOption(builder.Configuration, + InitServices.SettingSection) + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Common/tests/StaticInitTests.cs b/Common/tests/StaticInitTests.cs new file mode 100644 index 000000000..b0c5e2371 --- /dev/null +++ b/Common/tests/StaticInitTests.cs @@ -0,0 +1,302 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; + +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; +using ArmoniK.Core.Common.Utils; +using ArmoniK.Core.Utils; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +using NUnit.Framework; + +namespace ArmoniK.Core.Common.Tests; + +[TestFixture] +internal class StaticInitTests +{ + [SetUp] + public void SetUp() + { + Dictionary baseConfig = new() + { + { + $"{CertificatePath}:0", Cert1.ToJson() + }, + { + $"{CertificatePath}:1", Cert2.ToJson() + }, + { + $"{UserPath}:0", User1.ToJson() + }, + { + $"{UserPath}:1", User2.ToJson() + }, + { + $"{RolePath}:0", Role1.ToJson() + }, + { + $"{RolePath}:1", Role2.ToJson() + }, + { + $"{PartitionPath}:0", PartitionData1.ToJson() + }, + { + $"{PartitionPath}:1", PartitionData2.ToJson() + }, + }; + + Environment.SetEnvironmentVariable(CertificatePath.Replace(":", + "__") + "__2", + Cert3.ToJson()); + Environment.SetEnvironmentVariable(UserPath.Replace(":", + "__") + "__2", + User3.ToJson()); + Environment.SetEnvironmentVariable(RolePath.Replace(":", + "__") + "__2", + Role3.ToJson()); + Environment.SetEnvironmentVariable(PartitionPath.Replace(":", + "__") + "__2", + PartitionData3.ToJson()); + + configuration_ = new ConfigurationManager(); + configuration_.AddInMemoryCollection(baseConfig); + configuration_.AddEnvironmentVariables(); + + var services = new ServiceCollection(); + services.AddOption(configuration_, + InitServices.SettingSection); + services.AddSingleton(); + provider_ = services.BuildServiceProvider(); + + logger_ = new LoggerInit(configuration_).GetLogger(); + } + + private static readonly Certificate Cert1 = new() + { + Fingerprint = "Fingerprint1", + CN = "CN1", + User = "User1", + }; + + private static readonly Certificate Cert2 = new() + { + Fingerprint = "Fingerprint2", + CN = "CN2", + User = "User2", + }; + + private static readonly Certificate Cert3 = new() + { + Fingerprint = "Fingerprint3", + CN = "CN3", + User = "User3", + }; + + private static readonly User User1 = new() + { + Name = "User1", + Roles = new List + { + "Role1", + }, + }; + + private static readonly User User2 = new() + { + Name = "User2", + Roles = new List + { + "Role1", + "Role2", + }, + }; + + private static readonly User User3 = new() + { + Name = "User3", + Roles = new List + { + "Role3", + }, + }; + + private static readonly Role Role1 = new() + { + Name = "Role1", + Permissions = new List + { + "Perm1", + }, + }; + + private static readonly Role Role2 = new() + { + Name = "Role2", + Permissions = new List + { + "Perm1", + "Perm2", + }, + }; + + private static readonly Role Role3 = new() + { + Name = "Role3", + Permissions = new List + { + "Perm3", + }, + }; + + private static readonly Partition PartitionData1 = new() + { + Priority = 2, + PodMax = 2, + PodReserved = 2, + PreemptionPercentage = 2, + PartitionId = "Partition1", + ParentPartitionIds = new List + { + "PartitionParent1", + }, + PodConfiguration = new Dictionary + { + { + "key1", "val1" + }, + { + "key2", "val2" + }, + }, + }; + + private static readonly Partition PartitionData2 = new() + { + Priority = 2, + PodMax = 2, + PodReserved = 2, + PreemptionPercentage = 2, + PartitionId = "Partition2", + ParentPartitionIds = new List + { + "PartitionParent1", + "PartitionParent2", + }, + PodConfiguration = new Dictionary + { + { + "key1", "val1" + }, + { + "key3", "val3" + }, + }, + }; + + private static readonly Partition PartitionData3 = new() + { + Priority = 2, + PodMax = 2, + PodReserved = 2, + PreemptionPercentage = 2, + PartitionId = "Partition3", + ParentPartitionIds = new List + { + "PartitionParent1", + "PartitionParent3", + }, + }; + + private const string CertificatePath = $"{InitServices.SettingSection}:{Authentication.SettingSection}:{nameof(Authentication.UserCertificates)}"; + private const string UserPath = $"{InitServices.SettingSection}:{Authentication.SettingSection}:{nameof(Authentication.Users)}"; + private const string RolePath = $"{InitServices.SettingSection}:{Authentication.SettingSection}:{nameof(Authentication.Roles)}"; + private const string PartitionPath = $"{InitServices.SettingSection}:{Partitioning.SettingSection}:{nameof(Partitioning.Partitions)}"; + + private ServiceProvider? provider_; + private ConfigurationManager? configuration_; + private ILogger? logger_; + + [Test] + public void InitServicesNotNull() + { + var init = provider_!.GetRequiredService(); + Assert.NotNull(init); + Assert.True(init.InitDatabase); + Assert.True(init.InitObjectStorage); + Assert.True(init.InitQueue); + } + + [Test] + public void CountShouldBePositive() + { + var init = provider_!.GetRequiredService(); + logger_!.LogInformation("{@init}", + init); + Assert.AreEqual(3, + init.Authentication.UserCertificates.Count); + Assert.AreEqual(3, + init.Authentication.Users.Count); + Assert.AreEqual(3, + init.Authentication.Roles.Count); + Assert.AreEqual(3, + init.Partitioning.Partitions.Count); + + Assert.AreEqual(Cert1, + Certificate.FromJson(init.Authentication.UserCertificates[0])); + Assert.AreEqual(Cert2, + Certificate.FromJson(init.Authentication.UserCertificates[1])); + Assert.AreEqual(Cert3, + Certificate.FromJson(init.Authentication.UserCertificates[2])); + + Assert.AreEqual(User1, + User.FromJson(init.Authentication.Users[0])); + Assert.AreEqual(User2, + User.FromJson(init.Authentication.Users[1])); + Assert.AreEqual(User3, + User.FromJson(init.Authentication.Users[2])); + + Assert.AreEqual(Role1, + Role.FromJson(init.Authentication.Roles[0])); + Assert.AreEqual(Role2, + Role.FromJson(init.Authentication.Roles[1])); + Assert.AreEqual(Role3, + Role.FromJson(init.Authentication.Roles[2])); + + Assert.AreEqual(PartitionData1, + Partition.FromJson(init.Partitioning.Partitions[0])); + Assert.AreEqual(PartitionData2, + Partition.FromJson(init.Partitioning.Partitions[1])); + Assert.AreEqual(PartitionData3, + Partition.FromJson(init.Partitioning.Partitions[2])); + + var initDb = provider_!.GetRequiredService(); + Assert.AreEqual(3, + initDb.Users.Count); + Assert.AreEqual(3, + initDb.Auths.Count); + Assert.AreEqual(3, + initDb.Roles.Count); + Assert.AreEqual(3, + initDb.Partitions.Count); + } +} diff --git a/Common/tests/Submitter/SubmitterTests.cs b/Common/tests/Submitter/SubmitterTests.cs index fe9f173de..826741392 100644 --- a/Common/tests/Submitter/SubmitterTests.cs +++ b/Common/tests/Submitter/SubmitterTests.cs @@ -32,6 +32,8 @@ using ArmoniK.Core.Common.gRPC.Convertors; using ArmoniK.Core.Common.gRPC.Services; using ArmoniK.Core.Common.gRPC.Validators; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; using ArmoniK.Core.Common.Tests.Helpers; using ArmoniK.Core.Utils; @@ -128,6 +130,9 @@ public async Task SetUp() .AddSingleton() .AddOption(configuration, Injection.Options.Submitter.SettingSection) + .AddInitializedOption(configuration, + InitServices.SettingSection) + .AddSingleton() .AddSingleton(pushQueueStorage_); var provider = services.BuildServiceProvider(new ServiceProviderOptions diff --git a/Common/tests/TestBase/AuthenticationTableTestBase.cs b/Common/tests/TestBase/AuthenticationTableTestBase.cs index 04730f6de..7b76bd7b3 100644 --- a/Common/tests/TestBase/AuthenticationTableTestBase.cs +++ b/Common/tests/TestBase/AuthenticationTableTestBase.cs @@ -25,6 +25,8 @@ using ArmoniK.Core.Base.DataStructures; using ArmoniK.Core.Common.Auth.Authentication; using ArmoniK.Core.Common.Auth.Authorization.Permissions; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -43,6 +45,16 @@ private static bool CheckForSkipSetup() public async Task SetUp() { + Environment.SetEnvironmentVariable(CertificatePath.Replace(":", + "__") + "__0", + CertEnv.ToJson()); + Environment.SetEnvironmentVariable(UserPath.Replace(":", + "__") + "__0", + UserEnv.ToJson()); + Environment.SetEnvironmentVariable(RolePath.Replace(":", + "__") + "__0", + RoleEnv.ToJson()); + GetAuthSource(); if (!RunTests || CheckForSkipSetup()) @@ -61,6 +73,37 @@ public async Task SetUp() public virtual void TearDown() => RunTests = false; + private static readonly Certificate CertEnv = new() + { + Fingerprint = "FingerprintEnv", + CN = "CNEnv", + User = "UserEnv", + }; + + + private static readonly User UserEnv = new() + { + Name = "UserEnv", + Roles = new List + { + "RoleEnv", + }, + }; + + + private static readonly Role RoleEnv = new() + { + Name = "RoleEnv", + Permissions = new List + { + "PermEnv", + }, + }; + + private const string CertificatePath = $"{InitServices.SettingSection}:{Authentication.SettingSection}:{nameof(Authentication.UserCertificates)}"; + private const string UserPath = $"{InitServices.SettingSection}:{Authentication.SettingSection}:{nameof(Authentication.Users)}"; + private const string RolePath = $"{InitServices.SettingSection}:{Authentication.SettingSection}:{nameof(Authentication.Roles)}"; + static AuthenticationTableTestBase() { Roles = new List @@ -375,6 +418,9 @@ public void GetIdentityFromNameShouldFail(string name) [TestCase("User2", "Role1", true)] + [TestCase("UserEnv", + "RoleEnv", + true)] [TestCase("User2", "RoleDontExist", false)] diff --git a/Common/tests/TestBase/PartitionTableTestBase.cs b/Common/tests/TestBase/PartitionTableTestBase.cs index 99b05022f..fbea71f2f 100644 --- a/Common/tests/TestBase/PartitionTableTestBase.cs +++ b/Common/tests/TestBase/PartitionTableTestBase.cs @@ -15,6 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -24,6 +25,8 @@ using ArmoniK.Core.Base.DataStructures; using ArmoniK.Core.Common.Exceptions; using ArmoniK.Core.Common.gRPC; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; using ArmoniK.Core.Common.Tests.ListPartitionsRequestExt; @@ -39,6 +42,10 @@ public class PartitionTableTestBase [SetUp] public async Task SetUp() { + Environment.SetEnvironmentVariable(PartitionPath.Replace(":", + "__") + "__0", + PartitionEnv.ToJson()); + GetPartitionTableInstance(); if (!RunTests || CheckForSkipSetup()) @@ -105,6 +112,31 @@ public virtual void GetPartitionTableInstance() { } + private const string PartitionPath = $"{InitServices.SettingSection}:{Partitioning.SettingSection}:{nameof(Partitioning.Partitions)}"; + + + private static readonly Partition PartitionEnv = new() + { + Priority = 2, + PodMax = 2, + PodReserved = 2, + PreemptionPercentage = 2, + PartitionId = "Partition1", + ParentPartitionIds = new List + { + "PartitionParent1", + }, + PodConfiguration = new Dictionary + { + { + "key1", "val1" + }, + { + "key2", "val2" + }, + }, + }; + [Test] [Category("SkipSetUp")] public async Task InitShouldSucceed() @@ -151,6 +183,24 @@ public async Task ReadPartitionAsyncShouldSucceed() } } + [Test] + public async Task ReadPartitionAsyncFromEnvShouldSucceed() + { + if (RunTests) + { + var result = await PartitionTable!.ReadPartitionAsync(PartitionEnv.PartitionId, + CancellationToken.None) + .ConfigureAwait(false); + + Assert.AreEqual(PartitionEnv.PartitionId, + result.PartitionId); + Assert.AreEqual(PartitionEnv.PodReserved, + result.PodReserved); + Assert.AreEqual(PartitionEnv.PodConfiguration, + result.PodConfiguration!.Configuration); + } + } + [Test] public Task ReadTaskAsyncShouldThrowException() { diff --git a/Compute/PollingAgent/src/Program.cs b/Compute/PollingAgent/src/Program.cs index 9d552849f..cad0f4989 100644 --- a/Compute/PollingAgent/src/Program.cs +++ b/Compute/PollingAgent/src/Program.cs @@ -30,6 +30,8 @@ using ArmoniK.Core.Base.DataStructures; using ArmoniK.Core.Common.gRPC.Services; using ArmoniK.Core.Common.Injection; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Meter; using ArmoniK.Core.Common.Pollster; using ArmoniK.Core.Common.Pollster.TaskProcessingChecker; @@ -108,6 +110,9 @@ public static async Task Main(string[] args) .AddSingleton() .AddInitializedOption(builder.Configuration, Submitter.SettingSection) + .AddInitializedOption(builder.Configuration, + InitServices.SettingSection) + .AddSingleton() .AddSingleton(pollsterOptions) .AddSingleton(new ExceptionManager.Options(pollsterOptions.GraceDelay, pollsterOptions.MaxErrorAllowed)) diff --git a/Control/Metrics/src/Program.cs b/Control/Metrics/src/Program.cs index 7d6e45635..2a2c0a15a 100644 --- a/Control/Metrics/src/Program.cs +++ b/Control/Metrics/src/Program.cs @@ -22,6 +22,8 @@ using ArmoniK.Core.Adapters.MongoDB; using ArmoniK.Core.Base.DataStructures; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; using ArmoniK.Core.Common.Utils; using ArmoniK.Core.Control.Metrics.Options; @@ -64,6 +66,9 @@ public static async Task Main(string[] args) .AddMongoComponents(builder.Configuration, logger.GetLogger()) .AddSingleton(builder.Configuration.GetInitializedValue(MetricsExporter.SettingSection)) + .AddInitializedOption(builder.Configuration, + InitServices.SettingSection) + .AddSingleton() .AddHostedService() .AddControllers(); diff --git a/Control/PartitionMetrics/src/Program.cs b/Control/PartitionMetrics/src/Program.cs index 981cfea4f..69a9f7be7 100644 --- a/Control/PartitionMetrics/src/Program.cs +++ b/Control/PartitionMetrics/src/Program.cs @@ -22,6 +22,8 @@ using ArmoniK.Core.Adapters.MongoDB; using ArmoniK.Core.Base.DataStructures; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Storage; using ArmoniK.Core.Common.Utils; using ArmoniK.Core.Control.PartitionMetrics.Options; @@ -65,6 +67,9 @@ public static async Task Main(string[] args) logger.GetLogger()) .AddOption(builder.Configuration, MetricsExporter.SettingSection) + .AddInitializedOption(builder.Configuration, + InitServices.SettingSection) + .AddSingleton() .AddHostedService() .AddHttpClient() .AddControllers(); diff --git a/Control/Submitter/src/Program.cs b/Control/Submitter/src/Program.cs index 892906ca5..5af0b7e6f 100644 --- a/Control/Submitter/src/Program.cs +++ b/Control/Submitter/src/Program.cs @@ -32,6 +32,8 @@ using ArmoniK.Core.Common.gRPC; using ArmoniK.Core.Common.gRPC.Services; using ArmoniK.Core.Common.Injection; +using ArmoniK.Core.Common.Injection.Options; +using ArmoniK.Core.Common.Injection.Options.Database; using ArmoniK.Core.Common.Meter; using ArmoniK.Core.Common.Pollster; using ArmoniK.Core.Common.Storage; @@ -100,6 +102,9 @@ public static async Task Main(string[] args) .AddSingletonWithHealthCheck(nameof(ExceptionInterceptor)) .AddOption(builder.Configuration, Common.Injection.Options.Submitter.SettingSection) + .AddInitializedOption(builder.Configuration, + InitServices.SettingSection) + .AddSingleton() .AddSingleton(sp => new ExceptionManager.Options(TimeSpan.Zero, sp.GetRequiredService() .MaxErrorAllowed)) @@ -261,6 +266,12 @@ await authTable.Init(CancellationToken.None) await taskObjectFactory.ConfigureAwait(false); await taskPushQueueStorage.ConfigureAwait(false); + if (app.Services.GetRequiredService() + .StopAfterInit) + { + return 0; + } + await app.RunAsync() .ConfigureAwait(false); diff --git a/justfile b/justfile index 8b255cc74..bb351ff71 100644 --- a/justfile +++ b/justfile @@ -20,6 +20,7 @@ ingress := "true" prometheus := "true" grafana := "true" seq := "true" +cinit := "true" # Export them as terraform environment variables export TF_VAR_core_tag := tag @@ -30,6 +31,7 @@ export TF_VAR_num_partitions := partitions export TF_VAR_enable_grafana := grafana export TF_VAR_enable_seq := seq export TF_VAR_enable_prometheus := prometheus +export TF_VAR_container_init := cinit # Sets the queue diff --git a/terraform/locals.tf b/terraform/locals.tf index e5e81099e..e8fc1a95f 100644 --- a/terraform/locals.tf +++ b/terraform/locals.tf @@ -13,21 +13,22 @@ locals { "Serilog__MinimumLevel__Override__ArmoniK.Core.Common.Auth.Authentication.Authenticator" = "${var.serilog.loggin_level_routing}", "ASPNETCORE_ENVIRONMENT" = "${var.aspnet_core_env}" } - worker = merge(var.compute_plane.worker, { image = var.worker_image }) - queue = one(concat(module.queue_activemq, module.queue_rabbitmq, module.queue_artemis, module.queue_pubsub, module.queue_sqs, module.queue_none)) - database = module.database - object = one(concat(module.object_redis, module.object_minio, module.object_local)) + worker = merge(var.compute_plane.worker, { image = var.worker_image }) + queue = one(concat(module.queue_activemq, module.queue_rabbitmq, module.queue_artemis, module.queue_pubsub, module.queue_sqs, module.queue_none)) + database = module.database + object = one(concat(module.object_redis, module.object_minio, module.object_local)) + partition_env_vars = { for i in local.partitions : "InitServices__Partitioning__Partitions__${i}" => jsonencode(merge(var.partition_data, { PartitionId = "${var.partition_data.PartitionId}${i}" })) } env_maps = concat([ local.queue.generated_env_vars, local.object.generated_env_vars, local.database.generated_env_vars, local.logging_env_vars, - var.custom_env_vars + var.custom_env_vars, + local.partition_env_vars ], module.tracing[*].generated_env_vars) environment = merge(local.env_maps...) volumes = local.object.volumes submitter = merge(var.submitter, { tag = var.core_tag }) compute_plane = merge(var.compute_plane, { tag = var.core_tag }, { worker = local.worker }) - partition_list = { for i in local.partitions : i => merge(var.partition_data, { _id = "${var.partition_data._id}${i}" }) } polling_agent_names = toset([for v in module.compute_plane : v.polling_agent_name]) } diff --git a/terraform/main.tf b/terraform/main.tf index 56f9b4b2b..467aac05e 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -32,7 +32,6 @@ module "database" { image = var.database_image network = docker_network.armonik.id mongodb_params = var.mongodb_params - partition_list = local.partition_list } module "object_redis" { @@ -115,6 +114,7 @@ module "submitter" { generated_env_vars = local.environment log_driver = module.fluenbit.log_driver volumes = local.volumes + container_init = var.container_init } module "compute_plane" { @@ -129,6 +129,7 @@ module "compute_plane" { volumes = local.volumes network = docker_network.armonik.id log_driver = module.fluenbit.log_driver + container_init = var.container_init } module "metrics_exporter" { @@ -138,6 +139,7 @@ module "metrics_exporter" { network = docker_network.armonik.id generated_env_vars = local.environment log_driver = module.fluenbit.log_driver + container_init = var.container_init } module "partition_metrics_exporter" { @@ -148,6 +150,7 @@ module "partition_metrics_exporter" { generated_env_vars = local.environment metrics_env_vars = module.metrics_exporter.metrics_env_vars log_driver = module.fluenbit.log_driver + container_init = var.container_init } module "ingress" { diff --git a/terraform/modules/compute_plane/inputs.tf b/terraform/modules/compute_plane/inputs.tf index d599e9b0e..1320d30ed 100644 --- a/terraform/modules/compute_plane/inputs.tf +++ b/terraform/modules/compute_plane/inputs.tf @@ -50,3 +50,7 @@ variable "log_driver" { log_opts = map(string), }) } + +variable "container_init" { + type = bool +} diff --git a/terraform/modules/compute_plane/locals.tf b/terraform/modules/compute_plane/locals.tf index 4256b20e2..9458acfe3 100644 --- a/terraform/modules/compute_plane/locals.tf +++ b/terraform/modules/compute_plane/locals.tf @@ -8,6 +8,7 @@ locals { "InitWorker__WorkerCheckDelay=${var.polling_agent.worker_check_delay}", "Amqp__PartitionId=TestPartition${local.partition_chooser}", "PubSub__PartitionId=TestPartition${local.partition_chooser}", + "InitServices__InitDatabase=${!var.container_init}", ] common_env = [ "ComputePlane__WorkerChannel__SocketType=unixdomainsocket", diff --git a/terraform/modules/monitoring/metrics/inputs.tf b/terraform/modules/monitoring/metrics/inputs.tf index 3f7d806aa..84cbddb88 100644 --- a/terraform/modules/monitoring/metrics/inputs.tf +++ b/terraform/modules/monitoring/metrics/inputs.tf @@ -25,3 +25,7 @@ variable "log_driver" { log_opts = map(string), }) } + +variable "container_init" { + type = bool +} diff --git a/terraform/modules/monitoring/metrics/locals.tf b/terraform/modules/monitoring/metrics/locals.tf index bc0ffe04c..b5a00dd20 100644 --- a/terraform/modules/monitoring/metrics/locals.tf +++ b/terraform/modules/monitoring/metrics/locals.tf @@ -1,3 +1,6 @@ locals { + init_env = [ + "InitServices__InitDatabase=${!var.container_init}", + ] gen_env = [for k, v in var.generated_env_vars : "${k}=${v}"] -} \ No newline at end of file +} diff --git a/terraform/modules/monitoring/metrics/main.tf b/terraform/modules/monitoring/metrics/main.tf index 531674fbb..fa1670ec1 100644 --- a/terraform/modules/monitoring/metrics/main.tf +++ b/terraform/modules/monitoring/metrics/main.tf @@ -11,7 +11,7 @@ resource "docker_container" "metrics" { name = var.network } - env = local.gen_env + env = concat(local.gen_env, local.init_env) log_driver = var.log_driver.name log_opts = var.log_driver.log_opts diff --git a/terraform/modules/monitoring/partition_metrics/inputs.tf b/terraform/modules/monitoring/partition_metrics/inputs.tf index 3fbf4c6a3..2738e404d 100644 --- a/terraform/modules/monitoring/partition_metrics/inputs.tf +++ b/terraform/modules/monitoring/partition_metrics/inputs.tf @@ -29,3 +29,7 @@ variable "log_driver" { log_opts = map(string), }) } + +variable "container_init" { + type = bool +} diff --git a/terraform/modules/monitoring/partition_metrics/locals.tf b/terraform/modules/monitoring/partition_metrics/locals.tf index 4138bb4c7..468561575 100644 --- a/terraform/modules/monitoring/partition_metrics/locals.tf +++ b/terraform/modules/monitoring/partition_metrics/locals.tf @@ -1,4 +1,7 @@ locals { + init_env = [ + "InitServices__InitDatabase=${!var.container_init}", + ] env = [for k, v in var.metrics_env_vars : "${k}=${v}"] gen_env = [for k, v in var.generated_env_vars : "${k}=${v}"] -} \ No newline at end of file +} diff --git a/terraform/modules/monitoring/partition_metrics/main.tf b/terraform/modules/monitoring/partition_metrics/main.tf index 87d3fede5..b0f1230fb 100644 --- a/terraform/modules/monitoring/partition_metrics/main.tf +++ b/terraform/modules/monitoring/partition_metrics/main.tf @@ -11,7 +11,7 @@ resource "docker_container" "partition_metrics" { name = var.network } - env = concat(local.env, local.gen_env) + env = concat(local.env, local.gen_env, local.init_env) log_driver = var.log_driver.name log_opts = var.log_driver.log_opts diff --git a/terraform/modules/storage/database/mongo/inputs.tf b/terraform/modules/storage/database/mongo/inputs.tf index f8c6cc0b7..2292638fa 100644 --- a/terraform/modules/storage/database/mongo/inputs.tf +++ b/terraform/modules/storage/database/mongo/inputs.tf @@ -18,7 +18,3 @@ variable "mongodb_params" { windows = bool }) } - -variable "partition_list" { - type = map(any) -} diff --git a/terraform/modules/storage/database/mongo/main.tf b/terraform/modules/storage/database/mongo/main.tf index 388b9ca2d..a3fd30885 100644 --- a/terraform/modules/storage/database/mongo/main.tf +++ b/terraform/modules/storage/database/mongo/main.tf @@ -49,11 +49,3 @@ resource "null_resource" "init_replica" { } depends_on = [time_sleep.wait] } - -resource "null_resource" "partitions_in_db" { - for_each = var.partition_list - provisioner "local-exec" { - command = "${local.prefix_run} --eval 'db.PartitionData.insertOne(${jsonencode(each.value)})'" - } - depends_on = [null_resource.init_replica] -} diff --git a/terraform/modules/storage/database/mongo/outputs.tf b/terraform/modules/storage/database/mongo/outputs.tf index 364c42c83..4a4797f2d 100644 --- a/terraform/modules/storage/database/mongo/outputs.tf +++ b/terraform/modules/storage/database/mongo/outputs.tf @@ -11,5 +11,5 @@ output "generated_env_vars" { "MongoDB__ReplicaSet" = "${var.mongodb_params.replica_set_name}" } - depends_on = [null_resource.partitions_in_db] + depends_on = [null_resource.init_replica] } diff --git a/terraform/modules/submitter/inputs.tf b/terraform/modules/submitter/inputs.tf index 0db8bf044..557d2ea7b 100644 --- a/terraform/modules/submitter/inputs.tf +++ b/terraform/modules/submitter/inputs.tf @@ -29,3 +29,7 @@ variable "log_driver" { }) } +variable "container_init" { + type = bool +} + diff --git a/terraform/modules/submitter/locals.tf b/terraform/modules/submitter/locals.tf index 60d5c5bdf..7351c19b5 100644 --- a/terraform/modules/submitter/locals.tf +++ b/terraform/modules/submitter/locals.tf @@ -1,6 +1,13 @@ locals { - env = [ + common_env = [ "Submitter__DefaultPartition=TestPartition0", ] + control_plane_env = [ + "InitServices__InitDatabase=${!var.container_init}", + ] + init_env = [ + "InitServices__StopAfterInit=true", + "Serilog__Properties__Application=ArmoniK.Control.Init", + ] gen_env = [for k, v in var.generated_env_vars : "${k}=${v}"] } diff --git a/terraform/modules/submitter/main.tf b/terraform/modules/submitter/main.tf index 5f6f1c2e4..24a81a011 100644 --- a/terraform/modules/submitter/main.tf +++ b/terraform/modules/submitter/main.tf @@ -3,6 +3,31 @@ resource "docker_image" "submitter" { keep_locally = true } +resource "docker_container" "init" { + name = "${var.container_name}_init" + image = docker_image.submitter.image_id + count = var.container_init ? 1 : 0 + + networks_advanced { + name = var.network + } + + env = concat(local.common_env, local.gen_env, local.init_env) + + log_driver = var.log_driver.name + log_opts = var.log_driver.log_opts + must_run = false + + dynamic "mounts" { + for_each = var.volumes + content { + type = "volume" + target = mounts.value + source = mounts.key + } + } +} + resource "docker_container" "submitter" { name = var.container_name image = docker_image.submitter.image_id @@ -11,7 +36,7 @@ resource "docker_container" "submitter" { name = var.network } - env = concat(local.env, local.gen_env) + env = concat(local.common_env, local.gen_env, local.control_plane_env) log_driver = var.log_driver.name log_opts = var.log_driver.log_opts @@ -34,4 +59,6 @@ resource "docker_container" "submitter" { source = mounts.key } } + + depends_on = [docker_container.init] } diff --git a/terraform/variables.tf b/terraform/variables.tf index ea1eb649a..7dab51033 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -137,7 +137,7 @@ variable "compute_plane" { variable "partition_data" { description = "Template to create multiple partitions" type = object({ - _id = optional(string, "TestPartition") + PartitionId = optional(string, "TestPartition") Priority = optional(number, 1) PodReserved = optional(number, 50) PodMax = optional(number, 100) @@ -260,3 +260,8 @@ variable "tracing_ingestion_ports" { default = { } } + +variable "container_init" { + type = bool + default = true +}