From 07e49bd3ede6704e05766f95f5053b239c52f459 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Tue, 2 Dec 2025 11:07:40 +0500 Subject: [PATCH 01/46] feat: expand filtering --- .../DatabaseRegistryDbaasRepositoryImpl.java | 10 ++ .../dbaas/dto/backupV2/FilterCriteria.java | 10 +- .../dbaas/entity/pg/backup/FilterData.java | 12 ++ .../DatabaseRegistryDbaasRepository.java | 7 +- .../pg/jpa/DatabaseRegistryRepository.java | 55 +++++++- .../dbaas/service/DbBackupV2Service.java | 101 +++++++++----- .../v3/DatabaseBackupV2ControllerTest.java | 2 +- .../dbaas/service/DbBackupV2ServiceTest.java | 130 ++++++++++++++++++ docs/OpenAPI.json | 35 ----- 9 files changed, 280 insertions(+), 82 deletions(-) create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java index 5cc6b2e8..c9ad20f2 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java @@ -1,5 +1,7 @@ package com.netcracker.cloud.dbaas.dao.jpa; +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; import com.netcracker.cloud.dbaas.entity.pg.Database; import com.netcracker.cloud.dbaas.entity.pg.DatabaseRegistry; import com.netcracker.cloud.dbaas.repositories.dbaas.DatabaseRegistryDbaasRepository; @@ -141,6 +143,14 @@ public List findAllTransactionalDatabaseRegistries(String name return databaseRegistryRepository.findAllByNamespaceAndDatabase_BgVersionNull(namespace); } + @Override + public List findAllDatabasesByFilter(List namespaces, + List microserviceName, + List databaseType, + List databaseKind) { + return databaseRegistryRepository.findAllDatabasesByFilter(namespaces, microserviceName, databaseType, databaseKind); + } + @Override public void delete(DatabaseRegistry databaseRegistry) { log.debug("Delete logical database with classifier {} and type {}", databaseRegistry.getClassifier(), databaseRegistry.getType()); diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java index e963d383..3582bbb6 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.media.Schema; +import java.util.ArrayList; import java.util.List; @Data @@ -23,13 +24,10 @@ public class FilterCriteria { ) @NotNull(groups = {BackupGroup.class}) @Size(min = 1, groups = {BackupGroup.class}) - private List filter; - @Schema( - description = "Include databases that match any of the filters in the list" - ) - private List include; + private List filter = new ArrayList<>(); + @Schema( description = "Exclude databases that match any of the filters in the list" ) - private List exclude; + private List exclude = new ArrayList<>(); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java new file mode 100644 index 00000000..8ef4b12e --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java @@ -0,0 +1,12 @@ +package com.netcracker.cloud.dbaas.entity.pg.backup; + +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; + +import java.util.Set; + +public record FilterData( + Set namespaces, + Set microserviceNames, + Set databaseTypes, + Set databaseKinds) {} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java index 1978fc78..d363ad9e 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java @@ -1,15 +1,16 @@ package com.netcracker.cloud.dbaas.repositories.dbaas; +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; import com.netcracker.cloud.dbaas.entity.pg.Database; import com.netcracker.cloud.dbaas.entity.pg.DatabaseRegistry; +import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; -import javax.annotation.Nullable; - public interface DatabaseRegistryDbaasRepository { List findAnyLogDbRegistryTypeByNamespace(String namespace); @@ -63,4 +64,6 @@ public interface DatabaseRegistryDbaasRepository { List findAllTransactionalDatabaseRegistries(String namespace); + List findAllDatabasesByFilter(List namespaces, List microserviceName, + List databaseType, List databaseKind); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java index e9465383..e88708df 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java @@ -1,14 +1,13 @@ package com.netcracker.cloud.dbaas.repositories.pg.jpa; +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; +import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; import com.netcracker.cloud.dbaas.entity.pg.DatabaseRegistry; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; -import java.util.List; -import java.util.Optional; -import java.util.SortedMap; -import java.util.UUID; +import java.util.*; @ApplicationScoped @Transactional @@ -33,4 +32,52 @@ public List findAllByNamespaceAndDatabase_BgVersionNull(String public List findAllByNamespaceAndDatabase_BgVersionNotNull(String namespace) { return list("namespace = ?1 and database.bgVersion is not null", namespace); } + + public List findAllDatabasesByFilter( + List namespaces, + List microserviceNames, + List databaseTypes, + List databaseKinds) { + + StringBuilder q = new StringBuilder( + "SELECT cl.* " + + "FROM classifier cl " + + "LEFT JOIN database d ON cl.database_id = d.id " + + "WHERE 1=1 " + ); + + Map params = new HashMap<>(); + + if (namespaces != null && !namespaces.isEmpty()) { + q.append("AND cl.namespace = ANY(:namespaces) "); + params.put("namespaces", namespaces.toArray(new String[0])); + } + + if (microserviceNames != null && !microserviceNames.isEmpty()) { + q.append("AND cl.classifier->>'microserviceName' = ANY(:msNames) "); + params.put("msNames", microserviceNames.toArray(new String[0])); + } + + if (databaseTypes != null && !databaseTypes.isEmpty()) { + q.append("AND cl.type = ANY(:types) "); + params.put("types", databaseTypes.stream().map(DatabaseType::getType).toArray(String[]::new)); + } + + if (databaseKinds != null && databaseKinds.size() == 1) { + DatabaseKind kind = databaseKinds.getFirst(); + if (kind == DatabaseKind.CONFIGURATION) { + q.append("AND d.bgversion IS NOT NULL AND d.bgversion <> '' "); + } else if (kind == DatabaseKind.TRANSACTIONAL){ + q.append("AND (d.bgversion IS NULL OR d.bgversion = '') "); + } + } + + var query = getEntityManager() + .createNativeQuery(q.toString(), DatabaseRegistry.class); + + params.forEach(query::setParameter); + + return query.getResultList(); + } + } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index da065cb5..84ef42e3 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -5,6 +5,7 @@ import com.netcracker.cloud.dbaas.dto.backupV2.*; import com.netcracker.cloud.dbaas.entity.dto.backupV2.*; import com.netcracker.cloud.dbaas.entity.pg.*; +import com.netcracker.cloud.dbaas.entity.pg.backup.FilterData; import com.netcracker.cloud.dbaas.entity.pg.backupV2.*; import com.netcracker.cloud.dbaas.entity.shared.AbstractDatabase; import com.netcracker.cloud.dbaas.entity.shared.AbstractDatabaseRegistry; @@ -427,41 +428,80 @@ public BackupStatusResponse getCurrentStatus(String backupName) { } protected Map> getAllDbByFilter(FilterCriteria filterCriteria) { - Filter filter = filterCriteria.getFilter().getFirst(); - - if (filter.getNamespace().isEmpty()) { - if (!filter.getMicroserviceName().isEmpty()) { - throw new FunctionalityNotImplemented("backup by microservice"); - } - if (!filter.getDatabaseKind().isEmpty()) { - throw new FunctionalityNotImplemented("backup by databaseKind"); - } - if (!filter.getDatabaseType().isEmpty()) { - throw new FunctionalityNotImplemented("backup by databaseType"); - } - throw new RequestValidationException(ErrorCodes.CORE_DBAAS_4043, "namespace", Source.builder().build()); - } - if (filter.getNamespace().size() > 1) { - throw new FunctionalityNotImplemented("backup by several namespace"); + List databasesRegistriesForBackup = new ArrayList<>(); + + for (Filter filter : filterCriteria.getFilter()) { + databasesRegistriesForBackup.addAll( + databaseRegistryDbaasRepository + .findAllDatabasesByFilter( + filter.getNamespace(), + filter.getMicroserviceName(), + filter.getDatabaseType(), + filter.getDatabaseKind() + ) + .stream() + .filter(this::isValidRegistry) + .toList() + ); } + isEmptyDatabaseList(databasesRegistriesForBackup); - String namespace = filter.getNamespace().getFirst(); + FilterData exclude = collectFilter(filterCriteria.getExclude()); + + if(exclude.databaseKinds().size() == 2) { + databasesRegistriesForBackup = List.of(); + } - List databasesRegistriesForBackup = databaseRegistryDbaasRepository - .findAnyLogDbRegistryTypeByNamespace(namespace) + databasesRegistriesForBackup = databasesRegistriesForBackup .stream() - .filter(this::isValidRegistry) - .toList(); + .filter(databaseRegistry -> { + if (!exclude.databaseKinds().isEmpty()) { + DatabaseKind kind = exclude.databaseKinds().stream().toList().getFirst(); + String bgVersion = databaseRegistry.getDatabase().getBgVersion(); + boolean isConfig = bgVersion != null && !bgVersion.isBlank(); + boolean isTransactional = bgVersion == null || bgVersion.isBlank(); + + if (kind == DatabaseKind.CONFIGURATION && !isConfig) { + return false; + } - if (databasesRegistriesForBackup.isEmpty()) { - log.warn("During backup databases that match filterCriteria not found"); - throw new DbNotFoundException("Databases that match filterCriteria not found", Source.builder().build()); - } + if (kind == DatabaseKind.TRANSACTIONAL && !isTransactional) { + return false; + } + } + return !exclude.namespaces().contains(databaseRegistry.getNamespace()) && + !exclude.microserviceNames().contains((String) databaseRegistry.getClassifier().get(MICROSERVICE_NAME)) && + exclude.databaseTypes().stream().noneMatch(databaseType -> databaseType.getType().equals(databaseRegistry.getType())); + }).toList(); + + isEmptyDatabaseList(databasesRegistriesForBackup); return databasesRegistriesForBackup.stream() .collect(Collectors.groupingBy(DatabaseRegistry::getDatabase)); } + private void isEmptyDatabaseList(List databaseRegistries) { + if (databaseRegistries.isEmpty()) { + log.warn("No databases matching the filtering criteria were found during the backup"); + throw new DbNotFoundException("No databases matching the filtering criteria were found during the backup", Source.builder().build()); + } + } + + private FilterData collectFilter(List filterList) { + Set namespaces = new HashSet<>(); + Set microservices = new HashSet<>(); + Set databaseTypes = new HashSet<>(); + Set databaseKinds = new HashSet<>(); + + for (Filter filter : filterList) { + namespaces.addAll(filter.getNamespace()); + microservices.addAll(filter.getMicroserviceName()); + databaseTypes.addAll(filter.getDatabaseType()); + databaseKinds.addAll(filter.getDatabaseKind()); + } + return new FilterData(namespaces, microservices, databaseTypes, databaseKinds); + } + private boolean isValidRegistry(DatabaseRegistry registry) { return !registry.isMarkedForDrop() && !registry.getClassifier().containsKey(MARKED_FOR_DROP) @@ -1068,15 +1108,8 @@ private LogicalRestoreAdapterResponse logicalRestore(LogicalRestore logicalResto Restore restore = logicalRestore.getRestore(); RetryPolicy retryPolicy = buildRetryPolicy(logicalRestore.getLogicalRestoreName(), RESTORE_OPERATION); - try { - return Failsafe.with(retryPolicy) - .get(() -> executeRestore(logicalRestore, logicalBackupName, restore, databases, dryRun)); - } catch (Exception e) { - log.error("Logical restore startup for adapterId={} failed, restore={}", logicalRestore.getAdapterId(), restore.getName()); - throw new BackupExecutionException( - String.format("Logical restore startup for adapterId=%s failed, restore=%s", - logicalRestore.getAdapterId(), restore.getName()), e); - } + return Failsafe.with(retryPolicy) + .get(() -> executeRestore(logicalRestore, logicalBackupName, restore, databases, dryRun)); } private List> buildRestoreDatabases(LogicalRestore logicalRestore) { diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java index 3332a210..e1925131 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java @@ -86,7 +86,7 @@ void initiateBackup_invalidDto() { .statusCode(BAD_REQUEST.getStatusCode()) .body("message", allOf( containsString("backupName: must not be blank"), - containsString("filter: must not be null") + containsString("filter: size must be between 1 and 2147483647") )); verify(dbBackupV2Service, times(0)).backup(any(), anyBoolean()); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index 44f8333b..d2d0b6a7 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -1578,6 +1578,136 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { } + @Test + void getAllDbByFilter_() { + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + String namespace4 = "namespace4"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + String microserviceName4 = "microserviceName4"; + String microserviceName5 = "microserviceName5"; + String microserviceName6 = "microserviceName6"; + + String postgresqlType = "postgresql"; + String arangoDbType = "arangoDb"; + String cassandraType = "cassandra"; + + String adapterId = "adapterId"; + + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + String dbName4 = "db4"; + String dbName5 = "db5"; + + Database db1 = getDatabase(adapterId, dbName1, false, false, ""); + Database db2 = getDatabase(adapterId, dbName2, false, false, "cfg"); + Database db3 = getDatabase(adapterId, dbName3, false, false, "cfg"); + Database db4 = getDatabase(adapterId, dbName4, false, false, ""); + Database db5 = getDatabase(adapterId, dbName5, false, false, ""); + + DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); + DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace1, microserviceName2, "", postgresqlType); + DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName3, "", cassandraType); + DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace2, microserviceName4, "", cassandraType); + DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace3, microserviceName5, "", arangoDbType); + DatabaseRegistry registry6 = getDatabaseRegistry(db5, namespace4, microserviceName6, "", arangoDbType); + + Stream.of(registry1, registry2, registry3, registry4, registry5, registry6) + .forEach(databaseRegistryDbaasRepository::saveAnyTypeLogDb); + Filter filter = new Filter(); + filter.setNamespace(List.of(namespace1, namespace2)); + filter.setMicroserviceName(List.of(microserviceName1, microserviceName4)); + filter.setDatabaseType(List.of(DatabaseType.POSTGRESQL, DatabaseType.CASSANDRA)); + filter.setDatabaseKind(List.of(DatabaseKind.TRANSACTIONAL)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter)); + + Map> dbToBackup = dbBackupV2Service.getAllDbByFilter(filterCriteria); + assertNotNull(dbToBackup); + assertEquals(1, dbToBackup.size()); + + dbToBackup.forEach((db, registryList) -> { + assertEquals(db1.getId(), db.getId()); + assertEquals(1, registryList.size()); + assertEquals(registry1, registryList.getFirst()); + }); + } + + @Test + void getAllDbByFilter__() { + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + String namespace4 = "namespace4"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + String microserviceName4 = "microserviceName4"; + String microserviceName5 = "microserviceName5"; + String microserviceName6 = "microserviceName6"; + + String postgresqlType = "postgresql"; + String arangoDbType = "arangodb"; + String cassandraType = "cassandra"; + String adapterId = "adapterId"; + + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + String dbName4 = "db4"; + String dbName5 = "db5"; + + Database db1 = getDatabase(adapterId, dbName1, false, false, ""); + Database db2 = getDatabase(adapterId, dbName2, false, false, "cfg"); + Database db3 = getDatabase(adapterId, dbName3, false, false, "cfg"); + Database db4 = getDatabase(adapterId, dbName4, false, false, ""); + Database db5 = getDatabase(adapterId, dbName5, false, false, ""); + + DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); + DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace1, microserviceName2, "", postgresqlType); + DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName3, "", cassandraType); + DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace2, microserviceName4, "", cassandraType); + DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace3, microserviceName5, "", arangoDbType); + DatabaseRegistry registry6 = getDatabaseRegistry(db5, namespace4, microserviceName6, "", arangoDbType); + + Stream.of(registry1, registry2, registry3, registry4, registry5, registry6) + .forEach(databaseRegistryDbaasRepository::saveAnyTypeLogDb); + + Filter filter1 = new Filter(); + filter1.setNamespace(List.of(namespace1)); + filter1.setDatabaseType(List.of(DatabaseType.POSTGRESQL, DatabaseType.CASSANDRA)); + + Filter filter2 = new Filter(); + filter2.setNamespace(List.of(namespace2)); + filter2.setMicroserviceName(List.of(microserviceName3)); + filter2.setDatabaseType(List.of(DatabaseType.ARANGODB, DatabaseType.CASSANDRA)); + + Filter exclude = new Filter(); + exclude.setMicroserviceName(List.of(microserviceName1)); + exclude.setDatabaseType(List.of(DatabaseType.CASSANDRA)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter1, filter2)); + filterCriteria.setExclude(List.of(exclude)); + + Map> dbToBackup = dbBackupV2Service.getAllDbByFilter(filterCriteria); + assertNotNull(dbToBackup); + assertEquals(1, dbToBackup.size()); + + dbToBackup.forEach((db, registryList) -> { + assertEquals(db1.getId(), db.getId()); + assertEquals(1, registryList.size()); + assertEquals(registry2, registryList.getFirst()); + }); + } + @Test void getAllDbByFilter_whenDatabasesNotFound() { Filter filter = new Filter(); diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index d5a663fc..7066d0cd 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -338,13 +338,6 @@ }, "description": "Apply the filter to the remaining databases" }, - "include": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Filter" - }, - "description": "Include databases that match any of the filters in the list" - }, "exclude": { "type": "array", "items": { @@ -452,13 +445,6 @@ }, "description": "Apply the filter to the remaining databases" }, - "include": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Filter" - }, - "description": "Include databases that match any of the filters in the list" - }, "exclude": { "type": "array", "items": { @@ -1802,13 +1788,6 @@ }, "description": "Apply the filter to the remaining databases" }, - "include": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Filter" - }, - "description": "Include databases that match any of the filters in the list" - }, "exclude": { "type": "array", "items": { @@ -3282,13 +3261,6 @@ }, "description": "Apply the filter to the remaining databases" }, - "include": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Filter" - }, - "description": "Include databases that match any of the filters in the list" - }, "exclude": { "type": "array", "items": { @@ -3412,13 +3384,6 @@ }, "description": "Apply the filter to the remaining databases" }, - "include": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Filter" - }, - "description": "Include databases that match any of the filters in the list" - }, "exclude": { "type": "array", "items": { From 9765ff6487f6be91154234e7da6ad8755da2f988 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 8 Dec 2025 17:53:35 +0500 Subject: [PATCH 02/46] refactor: rewrite dynamic SQL filter --- .../DatabaseRegistryDbaasRepositoryImpl.java | 19 +- .../dbaas/entity/pg/backup/FilterData.java | 12 - .../DatabaseRegistryDbaasRepository.java | 6 +- .../pg/jpa/DatabaseRegistryRepository.java | 69 +-- .../dbaas/service/DbBackupV2Service.java | 163 +++--- .../dbaas/service/DbBackupV2ServiceTest.java | 488 +++++++++++++++++- 6 files changed, 581 insertions(+), 176 deletions(-) delete mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java index c9ad20f2..60a6777d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/DatabaseRegistryDbaasRepositoryImpl.java @@ -1,7 +1,6 @@ package com.netcracker.cloud.dbaas.dao.jpa; -import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; -import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; +import com.netcracker.cloud.dbaas.dto.backupV2.Filter; import com.netcracker.cloud.dbaas.entity.pg.Database; import com.netcracker.cloud.dbaas.entity.pg.DatabaseRegistry; import com.netcracker.cloud.dbaas.repositories.dbaas.DatabaseRegistryDbaasRepository; @@ -14,23 +13,18 @@ import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; - import net.jodah.failsafe.Failsafe; import net.jodah.failsafe.RetryPolicy; import org.apache.commons.lang.StringUtils; +import javax.annotation.Nullable; import java.time.Duration; import java.util.*; import java.util.concurrent.Callable; import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.Nullable; - -import static com.netcracker.cloud.dbaas.Constants.MICROSERVICE_NAME; -import static com.netcracker.cloud.dbaas.Constants.ROLE; -import static com.netcracker.cloud.dbaas.Constants.SCOPE; -import static com.netcracker.cloud.dbaas.Constants.SCOPE_VALUE_TENANT; +import static com.netcracker.cloud.dbaas.Constants.*; import static com.netcracker.cloud.dbaas.config.ServicesConfig.DBAAS_REPOSITORIES_MUTEX; import static jakarta.transaction.Transactional.TxType.REQUIRES_NEW; @@ -144,11 +138,8 @@ public List findAllTransactionalDatabaseRegistries(String name } @Override - public List findAllDatabasesByFilter(List namespaces, - List microserviceName, - List databaseType, - List databaseKind) { - return databaseRegistryRepository.findAllDatabasesByFilter(namespaces, microserviceName, databaseType, databaseKind); + public List findAllDatabasesByFilter(List filters) { + return databaseRegistryRepository.findAllDatabasesByFilter(filters); } @Override diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java deleted file mode 100644 index 8ef4b12e..00000000 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backup/FilterData.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.netcracker.cloud.dbaas.entity.pg.backup; - -import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; -import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; - -import java.util.Set; - -public record FilterData( - Set namespaces, - Set microserviceNames, - Set databaseTypes, - Set databaseKinds) {} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java index d363ad9e..190c6f5c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/DatabaseRegistryDbaasRepository.java @@ -1,7 +1,6 @@ package com.netcracker.cloud.dbaas.repositories.dbaas; -import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; -import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; +import com.netcracker.cloud.dbaas.dto.backupV2.Filter; import com.netcracker.cloud.dbaas.entity.pg.Database; import com.netcracker.cloud.dbaas.entity.pg.DatabaseRegistry; @@ -64,6 +63,5 @@ public interface DatabaseRegistryDbaasRepository { List findAllTransactionalDatabaseRegistries(String namespace); - List findAllDatabasesByFilter(List namespaces, List microserviceName, - List databaseType, List databaseKind); + List findAllDatabasesByFilter(List filters); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java index e88708df..96f9a504 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java @@ -2,13 +2,19 @@ import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; +import com.netcracker.cloud.dbaas.dto.backupV2.Filter; import com.netcracker.cloud.dbaas.entity.pg.DatabaseRegistry; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; import java.util.*; +import static com.netcracker.cloud.dbaas.Constants.MICROSERVICE_NAME; +import static com.netcracker.cloud.dbaas.Constants.NAMESPACE; + +@Slf4j @ApplicationScoped @Transactional public class DatabaseRegistryRepository implements PanacheRepositoryBase { @@ -33,51 +39,52 @@ public List findAllByNamespaceAndDatabase_BgVersionNotNull(Str return list("namespace = ?1 and database.bgVersion is not null", namespace); } - public List findAllDatabasesByFilter( - List namespaces, - List microserviceNames, - List databaseTypes, - List databaseKinds) { - + public List findAllDatabasesByFilter(List filters) { StringBuilder q = new StringBuilder( "SELECT cl.* " + "FROM classifier cl " + "LEFT JOIN database d ON cl.database_id = d.id " + - "WHERE 1=1 " + "WHERE " ); + int index = 0; + List orBlock = new ArrayList<>(); Map params = new HashMap<>(); - if (namespaces != null && !namespaces.isEmpty()) { - q.append("AND cl.namespace = ANY(:namespaces) "); - params.put("namespaces", namespaces.toArray(new String[0])); - } - - if (microserviceNames != null && !microserviceNames.isEmpty()) { - q.append("AND cl.classifier->>'microserviceName' = ANY(:msNames) "); - params.put("msNames", microserviceNames.toArray(new String[0])); - } - - if (databaseTypes != null && !databaseTypes.isEmpty()) { - q.append("AND cl.type = ANY(:types) "); - params.put("types", databaseTypes.stream().map(DatabaseType::getType).toArray(String[]::new)); - } - - if (databaseKinds != null && databaseKinds.size() == 1) { - DatabaseKind kind = databaseKinds.getFirst(); - if (kind == DatabaseKind.CONFIGURATION) { - q.append("AND d.bgversion IS NOT NULL AND d.bgversion <> '' "); - } else if (kind == DatabaseKind.TRANSACTIONAL){ - q.append("AND (d.bgversion IS NULL OR d.bgversion = '') "); + for (Filter filter : filters) { + List query = new ArrayList<>(); + if (filter.getNamespace() != null && !filter.getNamespace().isEmpty()) { + String nsValues = "nsValues" + index; + query.add("cl.classifier #>> '{" + NAMESPACE + "}' = ANY(:" + nsValues + ")"); + params.put(nsValues, filter.getNamespace().toArray(new String[0])); + } + if (filter.getMicroserviceName() != null && !filter.getMicroserviceName().isEmpty()) { + String msValues = "msValues" + index; + query.add("cl.classifier #>> '{" + MICROSERVICE_NAME + "}' = ANY(:" + msValues + ")"); + params.put(msValues, filter.getMicroserviceName().toArray(new String[0])); + } + if (filter.getDatabaseType() != null && !filter.getDatabaseType().isEmpty()) { + String typeValues = "typeValues" + index; + query.add("cl.type = ANY(:" + typeValues + ")"); + params.put(typeValues, filter.getDatabaseType().stream().map(DatabaseType::getType).toList().toArray(new String[0])); + } + if (filter.getDatabaseKind() != null && filter.getDatabaseKind().size() == 1) { + DatabaseKind kind = filter.getDatabaseKind().getFirst(); + if (kind == DatabaseKind.CONFIGURATION) { + query.add("d.bgversion IS NOT NULL AND d.bgversion <> '' "); + } else if (kind == DatabaseKind.TRANSACTIONAL) { + query.add("(d.bgversion IS NULL OR d.bgversion = '') "); + } } + String block = "(" + String.join(" AND ", query) + ")"; + orBlock.add(block); + index++; } + q.append(String.join(" OR ", orBlock)); var query = getEntityManager() .createNativeQuery(q.toString(), DatabaseRegistry.class); - params.forEach(query::setParameter); - return query.getResultList(); } - } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 84ef42e3..48e5ec4c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -5,7 +5,6 @@ import com.netcracker.cloud.dbaas.dto.backupV2.*; import com.netcracker.cloud.dbaas.entity.dto.backupV2.*; import com.netcracker.cloud.dbaas.entity.pg.*; -import com.netcracker.cloud.dbaas.entity.pg.backup.FilterData; import com.netcracker.cloud.dbaas.entity.pg.backupV2.*; import com.netcracker.cloud.dbaas.entity.shared.AbstractDatabase; import com.netcracker.cloud.dbaas.entity.shared.AbstractDatabaseRegistry; @@ -43,7 +42,6 @@ import static com.netcracker.cloud.dbaas.Constants.*; import static com.netcracker.cloud.dbaas.entity.shared.AbstractDbState.DatabaseStateStatus.CREATED; -import static com.netcracker.cloud.dbaas.service.DBaaService.MARKED_FOR_DROP; import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP; @Slf4j @@ -428,84 +426,65 @@ public BackupStatusResponse getCurrentStatus(String backupName) { } protected Map> getAllDbByFilter(FilterCriteria filterCriteria) { - List databasesRegistriesForBackup = new ArrayList<>(); - - for (Filter filter : filterCriteria.getFilter()) { - databasesRegistriesForBackup.addAll( - databaseRegistryDbaasRepository - .findAllDatabasesByFilter( - filter.getNamespace(), - filter.getMicroserviceName(), - filter.getDatabaseType(), - filter.getDatabaseKind() - ) - .stream() - .filter(this::isValidRegistry) - .toList() - ); - } - isEmptyDatabaseList(databasesRegistriesForBackup); - - FilterData exclude = collectFilter(filterCriteria.getExclude()); - - if(exclude.databaseKinds().size() == 2) { - databasesRegistriesForBackup = List.of(); + int uniqKinds = (int) filterCriteria.getExclude().stream() + .flatMap(exclude -> exclude.getDatabaseKind().stream()) + .distinct() + .count(); + if (uniqKinds == 2){ + log.warn("No databases matching the filtering criteria were found during the backup"); + throw new DbNotFoundException("No databases matching the filtering criteria were found during the backup", Source.builder().build()); } - - databasesRegistriesForBackup = databasesRegistriesForBackup + log.info(filterCriteria.toString()); + List filteredDatabases = databaseRegistryDbaasRepository + .findAllDatabasesByFilter(filterCriteria.getFilter()) .stream() - .filter(databaseRegistry -> { - if (!exclude.databaseKinds().isEmpty()) { - DatabaseKind kind = exclude.databaseKinds().stream().toList().getFirst(); - String bgVersion = databaseRegistry.getDatabase().getBgVersion(); - boolean isConfig = bgVersion != null && !bgVersion.isBlank(); - boolean isTransactional = bgVersion == null || bgVersion.isBlank(); - - if (kind == DatabaseKind.CONFIGURATION && !isConfig) { - return false; - } - - if (kind == DatabaseKind.TRANSACTIONAL && !isTransactional) { - return false; - } - } + .filter(registry -> filterCriteria.getExclude().stream().noneMatch(exclude -> { + boolean configurational = registry.getBgVersion() != null && !registry.getBgVersion().isBlank(); + return isMatches(exclude, + (String) registry.getClassifier().get(NAMESPACE), + (String) registry.getClassifier().get(MICROSERVICE_NAME), + registry.getType(), + configurational); + })) + .toList(); - return !exclude.namespaces().contains(databaseRegistry.getNamespace()) && - !exclude.microserviceNames().contains((String) databaseRegistry.getClassifier().get(MICROSERVICE_NAME)) && - exclude.databaseTypes().stream().noneMatch(databaseType -> databaseType.getType().equals(databaseRegistry.getType())); - }).toList(); + if (filteredDatabases.isEmpty()) { + log.warn("No databases matching the filtering criteria were found during the backup"); + throw new DbNotFoundException("No databases matching the filtering criteria were found during the backup", Source.builder().build()); + } - isEmptyDatabaseList(databasesRegistriesForBackup); - return databasesRegistriesForBackup.stream() + return filteredDatabases.stream() .collect(Collectors.groupingBy(DatabaseRegistry::getDatabase)); } - private void isEmptyDatabaseList(List databaseRegistries) { - if (databaseRegistries.isEmpty()) { - log.warn("No databases matching the filtering criteria were found during the backup"); - throw new DbNotFoundException("No databases matching the filtering criteria were found during the backup", Source.builder().build()); + private boolean isMatches(Filter filter, String namespace, String microserviceName, String type, boolean configurational) { + if (!filter.getNamespace().isEmpty() && + !filter.getNamespace().contains(namespace)) { + return false; } - } - private FilterData collectFilter(List filterList) { - Set namespaces = new HashSet<>(); - Set microservices = new HashSet<>(); - Set databaseTypes = new HashSet<>(); - Set databaseKinds = new HashSet<>(); + if (!filter.getMicroserviceName().isEmpty() && + !filter.getMicroserviceName().contains(microserviceName)) { + return false; + } + + if (!filter.getDatabaseType().isEmpty() && + filter.getDatabaseType().stream().noneMatch(dt -> dt.getType().equals(type))) { + return false; + } - for (Filter filter : filterList) { - namespaces.addAll(filter.getNamespace()); - microservices.addAll(filter.getMicroserviceName()); - databaseTypes.addAll(filter.getDatabaseType()); - databaseKinds.addAll(filter.getDatabaseKind()); + if (!filter.getDatabaseKind().isEmpty()) { + return isKindMatched(configurational, filter.getDatabaseKind().getFirst()); } - return new FilterData(namespaces, microservices, databaseTypes, databaseKinds); + return true; } - private boolean isValidRegistry(DatabaseRegistry registry) { - return !registry.isMarkedForDrop() - && !registry.getClassifier().containsKey(MARKED_FOR_DROP) - && CREATED.equals(registry.getDbState().getDatabaseState()); + private boolean isKindMatched(boolean configurational, DatabaseKind kind) { + if (kind == DatabaseKind.CONFIGURATION) + return configurational; + if (kind == DatabaseKind.TRANSACTIONAL) + return !configurational; + return true; } public BackupResponse getBackup(String backupName) { @@ -723,44 +702,30 @@ protected List getAllDbByFilter(List bac return backupDatabasesToFilter.stream().map(db -> new BackupDatabaseDelegate(db, db.getClassifiers())) .toList(); - Filter filter = filterCriteria.getFilter().getFirst(); - - if (filter.getNamespace().isEmpty()) { - if (!filter.getMicroserviceName().isEmpty()) { - throw new FunctionalityNotImplemented("restoration by microservice"); - } - if (!filter.getDatabaseKind().isEmpty()) { - throw new FunctionalityNotImplemented("restoration by databaseKind"); - } - if (!filter.getDatabaseType().isEmpty()) { - throw new FunctionalityNotImplemented("restoration by databaseType"); - } - throw new RequestValidationException(ErrorCodes.CORE_DBAAS_4043, "namespace", Source.builder().build()); - } - if (filter.getNamespace().size() > 1) { - throw new FunctionalityNotImplemented("restoration by several namespace"); - } - String namespace = filter.getNamespace().getFirst(); - // Filter BackupDatabase by namespace List databaseDelegateList = backupDatabasesToFilter.stream() - .map(backupDatabase -> { - List> filteredClassifiers = backupDatabase.getClassifiers().stream() - .filter(classifier -> namespace.equals(classifier.get(NAMESPACE))) - .map(classifier -> (SortedMap) new TreeMap<>(classifier)) - .toList(); + .map(db -> { + List> filteredClassifiers = db.getClassifiers().stream() + .filter(classifier -> { + String namespace = (String) classifier.get(NAMESPACE); + String microserviceName = (String) classifier.get(MICROSERVICE_NAME); + String type = db.getLogicalBackup().getType(); + boolean configurational = db.isConfigurational(); + return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) + && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); + }) + .map(c -> (SortedMap) new TreeMap<>(c)) + .toList(); - if (filteredClassifiers.isEmpty()) - return null; + if (filteredClassifiers.isEmpty()) { + return null; + } - return new BackupDatabaseDelegate( - backupDatabase, - filteredClassifiers - ); - } - ) + return new BackupDatabaseDelegate(db, filteredClassifiers); + }) .filter(Objects::nonNull) .toList(); + if (databaseDelegateList.isEmpty()) { log.warn("During restore databases that match filterCriteria not found"); throw new DbNotFoundException("Databases that match filterCriteria not found", Source.builder().build()); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index d2d0b6a7..332b1c99 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -2,6 +2,7 @@ import com.netcracker.cloud.dbaas.dto.EnsuredUser; import com.netcracker.cloud.dbaas.dto.backupV2.*; +import com.netcracker.cloud.dbaas.entity.dto.backupV2.BackupDatabaseDelegate; import com.netcracker.cloud.dbaas.entity.dto.backupV2.LogicalBackupAdapterResponse; import com.netcracker.cloud.dbaas.entity.dto.backupV2.LogicalRestoreAdapterResponse; import com.netcracker.cloud.dbaas.entity.pg.*; @@ -1579,14 +1580,13 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { } @Test - void getAllDbByFilter_() { + void getAllDbByFilter_1() { String namespace1 = "namespace1"; String namespace2 = "namespace2"; String namespace3 = "namespace3"; String namespace4 = "namespace4"; String microserviceName1 = "microserviceName1"; - String microserviceName2 = "microserviceName2"; String microserviceName3 = "microserviceName3"; String microserviceName4 = "microserviceName4"; String microserviceName5 = "microserviceName5"; @@ -1611,7 +1611,7 @@ void getAllDbByFilter_() { Database db5 = getDatabase(adapterId, dbName5, false, false, ""); DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); - DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace1, microserviceName2, "", postgresqlType); + DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace2, microserviceName1, "", postgresqlType); DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName3, "", cassandraType); DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace2, microserviceName4, "", cassandraType); DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace3, microserviceName5, "", arangoDbType); @@ -1619,35 +1619,41 @@ void getAllDbByFilter_() { Stream.of(registry1, registry2, registry3, registry4, registry5, registry6) .forEach(databaseRegistryDbaasRepository::saveAnyTypeLogDb); + Filter filter = new Filter(); filter.setNamespace(List.of(namespace1, namespace2)); filter.setMicroserviceName(List.of(microserviceName1, microserviceName4)); filter.setDatabaseType(List.of(DatabaseType.POSTGRESQL, DatabaseType.CASSANDRA)); filter.setDatabaseKind(List.of(DatabaseKind.TRANSACTIONAL)); + Filter exclude = new Filter(); + exclude.setNamespace(List.of(namespace2)); + exclude.setMicroserviceName(List.of(microserviceName1)); + FilterCriteria filterCriteria = new FilterCriteria(); filterCriteria.setFilter(List.of(filter)); + filterCriteria.setExclude(List.of(exclude)); Map> dbToBackup = dbBackupV2Service.getAllDbByFilter(filterCriteria); assertNotNull(dbToBackup); assertEquals(1, dbToBackup.size()); - dbToBackup.forEach((db, registryList) -> { + dbToBackup.forEach((db, registries) -> { assertEquals(db1.getId(), db.getId()); - assertEquals(1, registryList.size()); - assertEquals(registry1, registryList.getFirst()); + assertEquals(1, registries.size()); + assertEquals(registry1, db.getDatabaseRegistry().getFirst()); + assertEquals(registry1, registries.getFirst()); }); } @Test - void getAllDbByFilter__() { + void getAllDbByFilter_2() { String namespace1 = "namespace1"; String namespace2 = "namespace2"; String namespace3 = "namespace3"; String namespace4 = "namespace4"; String microserviceName1 = "microserviceName1"; - String microserviceName2 = "microserviceName2"; String microserviceName3 = "microserviceName3"; String microserviceName4 = "microserviceName4"; String microserviceName5 = "microserviceName5"; @@ -1671,7 +1677,7 @@ void getAllDbByFilter__() { Database db5 = getDatabase(adapterId, dbName5, false, false, ""); DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); - DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace1, microserviceName2, "", postgresqlType); + DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace2, microserviceName1, "", postgresqlType); DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName3, "", cassandraType); DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace2, microserviceName4, "", cassandraType); DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace3, microserviceName5, "", arangoDbType); @@ -1686,28 +1692,214 @@ void getAllDbByFilter__() { Filter filter2 = new Filter(); filter2.setNamespace(List.of(namespace2)); - filter2.setMicroserviceName(List.of(microserviceName3)); - filter2.setDatabaseType(List.of(DatabaseType.ARANGODB, DatabaseType.CASSANDRA)); + filter2.setDatabaseType(List.of(DatabaseType.POSTGRESQL, DatabaseType.CASSANDRA)); Filter exclude = new Filter(); exclude.setMicroserviceName(List.of(microserviceName1)); - exclude.setDatabaseType(List.of(DatabaseType.CASSANDRA)); + + Filter exclude2 = new Filter(); + exclude2.setNamespace(List.of(namespace2)); + exclude2.setMicroserviceName(List.of(microserviceName4)); FilterCriteria filterCriteria = new FilterCriteria(); filterCriteria.setFilter(List.of(filter1, filter2)); - filterCriteria.setExclude(List.of(exclude)); + filterCriteria.setExclude(List.of(exclude, exclude2)); Map> dbToBackup = dbBackupV2Service.getAllDbByFilter(filterCriteria); assertNotNull(dbToBackup); assertEquals(1, dbToBackup.size()); - dbToBackup.forEach((db, registryList) -> { + dbToBackup.forEach((db, registries) -> { + assertEquals(db2.getId(), db.getId()); + assertEquals(1, registries.size()); + assertEquals(registry3, registries.getFirst()); + }); + } + + @Test + void getAllDbByFilter_3() { + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + String namespace4 = "namespace4"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + String microserviceName4 = "microserviceName4"; + String microserviceName5 = "microserviceName5"; + + String postgresqlType = "postgresql"; + String arangoDbType = "arangodb"; + String cassandraType = "cassandra"; + String adapterId = "adapterId"; + + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + String dbName4 = "db4"; + String dbName5 = "db5"; + + Database db1 = getDatabase(adapterId, dbName1, false, false, ""); + Database db2 = getDatabase(adapterId, dbName2, false, false, "cfg"); + Database db3 = getDatabase(adapterId, dbName3, false, false, "cfg"); + Database db4 = getDatabase(adapterId, dbName4, false, false, ""); + Database db5 = getDatabase(adapterId, dbName5, false, false, ""); + + DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); + DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace2, microserviceName1, "", postgresqlType); + DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName2, "", cassandraType); + DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace3, microserviceName3, "", cassandraType); + DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace4, microserviceName4, "", arangoDbType); + DatabaseRegistry registry6 = getDatabaseRegistry(db5, namespace4, microserviceName5, "", arangoDbType); + + Stream.of(registry1, registry2, registry3, registry4, registry5, registry6) + .forEach(databaseRegistryDbaasRepository::saveAnyTypeLogDb); + + Filter filter = new Filter(); + filter.setNamespace(List.of(namespace1, namespace2)); + filter.setMicroserviceName(List.of(microserviceName1)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter)); + + Map> filteredDbs = dbBackupV2Service.getAllDbByFilter(filterCriteria); + assertEquals(1, filteredDbs.size()); + + filteredDbs.forEach((db, registries) -> { + if (db.getId() == db1.getId()) { + assertEquals(db1.getId(), db.getId()); + assertEquals(2, registries.size()); + DatabaseRegistry actualRegistry1 = filteredDbs.get(db1).stream() + .filter(r -> namespace1.equals(r.getNamespace())) + .findAny().orElse(null); + + DatabaseRegistry actualRegistry2 = filteredDbs.get(db1).stream() + .filter(r -> namespace2.equals(r.getNamespace())) + .findAny().orElse(null); + assertEquals(registry1, actualRegistry1); + assertEquals(registry2, actualRegistry2); + } else { + assertEquals(db1.getId(), db.getId()); + } + }); + } + + @Test + void getAllDbByFilter_4() { + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + String namespace4 = "namespace4"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + String microserviceName4 = "microserviceName4"; + String microserviceName5 = "microserviceName5"; + String microserviceName6 = "microserviceName6"; + + String postgresqlType = "postgresql"; + String arangoDbType = "arangodb"; + String cassandraType = "cassandra"; + String adapterId = "adapterId"; + + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + String dbName4 = "db4"; + String dbName5 = "db5"; + + Database db1 = getDatabase(adapterId, dbName1, false, false, ""); + Database db2 = getDatabase(adapterId, dbName2, false, false, "cfg"); + Database db3 = getDatabase(adapterId, dbName3, false, false, "cfg"); + Database db4 = getDatabase(adapterId, dbName4, false, false, ""); + Database db5 = getDatabase(adapterId, dbName5, false, false, ""); + + DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); + DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace1, microserviceName2, "", postgresqlType); + DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName3, "", cassandraType); + DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace2, microserviceName4, "", cassandraType); + DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace3, microserviceName5, "", arangoDbType); + DatabaseRegistry registry6 = getDatabaseRegistry(db5, namespace4, microserviceName6, "", arangoDbType); + + Stream.of(registry1, registry2, registry3, registry4, registry5, registry6) + .forEach(databaseRegistryDbaasRepository::saveAnyTypeLogDb); + + Filter filter = new Filter(); + filter.setNamespace(List.of(namespace1)); + + Filter filter1 = new Filter(); + filter1.setNamespace(List.of(namespace1)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter, filter1)); + + Map> allDbByFilter = dbBackupV2Service.getAllDbByFilter(filterCriteria); + + assertEquals(1, allDbByFilter.size()); + + allDbByFilter.forEach((db, registries) -> { assertEquals(db1.getId(), db.getId()); - assertEquals(1, registryList.size()); - assertEquals(registry2, registryList.getFirst()); + assertEquals(2, registries.size()); }); } + @Test + void getAllDbByFilter_5() { + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + String namespace4 = "namespace4"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + String microserviceName4 = "microserviceName4"; + String microserviceName5 = "microserviceName5"; + String microserviceName6 = "microserviceName6"; + + String postgresqlType = "postgresql"; + String arangoDbType = "arangodb"; + String cassandraType = "cassandra"; + String adapterId = "adapterId"; + + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + String dbName4 = "db4"; + String dbName5 = "db5"; + + Database db1 = getDatabase(adapterId, dbName1, false, false, ""); + Database db2 = getDatabase(adapterId, dbName2, false, false, "cfg"); + Database db3 = getDatabase(adapterId, dbName3, false, false, "cfg"); + Database db4 = getDatabase(adapterId, dbName4, false, false, ""); + Database db5 = getDatabase(adapterId, dbName5, false, false, ""); + + DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); + DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace1, microserviceName2, "", postgresqlType); + DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName3, "", cassandraType); + DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace2, microserviceName4, "", cassandraType); + DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace3, microserviceName5, "", arangoDbType); + DatabaseRegistry registry6 = getDatabaseRegistry(db5, namespace4, microserviceName6, "", arangoDbType); + + Stream.of(registry1, registry2, registry3, registry4, registry5, registry6) + .forEach(databaseRegistryDbaasRepository::saveAnyTypeLogDb); + + Filter filter = new Filter(); + filter.setDatabaseKind(List.of(DatabaseKind.TRANSACTIONAL, DatabaseKind.CONFIGURATION)); + + Filter exclude = new Filter(); + exclude.setDatabaseKind(List.of(DatabaseKind.CONFIGURATION, DatabaseKind.TRANSACTIONAL, DatabaseKind.TRANSACTIONAL)); //To check distinct method + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter)); + filterCriteria.setExclude(List.of(exclude)); + + assertThrows(DbNotFoundException.class, + () -> dbBackupV2Service.getAllDbByFilter(filterCriteria)); + } + @Test void getAllDbByFilter_whenDatabasesNotFound() { Filter filter = new Filter(); @@ -1719,6 +1911,269 @@ void getAllDbByFilter_whenDatabasesNotFound() { () -> dbBackupV2Service.getAllDbByFilter(filterCriteria)); } + @Test + void getAllDbByFilter_RestorePart_1() { + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + String dbName4 = "db4"; + String dbName5 = "db5"; + String dbName6 = "db6"; + + String logicalBackupName1 = "lb1"; + String logicalBackupName2 = "db2"; + String logicalBackupName3 = "db3"; + + String adapterId1 = "adpater1"; + String adapterId2 = "adapter2"; + String adapterId3 = "adapter3"; + + String postgresqlType = "postgresql"; + String cassandraType = "cassandra"; + String arangoType = "arangodb"; + + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + String namespace4 = "namespace4"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + String microserviceName4 = "microserviceName4"; + String microserviceName5 = "microserviceName5"; + String microserviceName6 = "microserviceName6"; + + BackupDatabase backupDatabase1 = getBackupDatabase(dbName1, List.of(getClassifier(namespace1, microserviceName1, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase2 = getBackupDatabase(dbName2, List.of(getClassifier(namespace1, microserviceName2, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase3 = getBackupDatabase(dbName3, List.of(getClassifier(namespace2, microserviceName3, null)), true, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase4 = getBackupDatabase(dbName4, List.of(getClassifier(namespace2, microserviceName4, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase5 = getBackupDatabase(dbName5, List.of(getClassifier(namespace3, microserviceName5, null)), true, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase6 = getBackupDatabase(dbName6, List.of(getClassifier(namespace4, microserviceName6, null)), false, BackupTaskStatus.COMPLETED, null); + + LogicalBackup logicalBackup1 = getLogicalBackup(logicalBackupName1, adapterId1, postgresqlType, List.of(backupDatabase1, backupDatabase2), BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup2 = getLogicalBackup(logicalBackupName2, adapterId2, cassandraType, List.of(backupDatabase3, backupDatabase4), BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup3 = getLogicalBackup(logicalBackupName3, adapterId3, arangoType, List.of(backupDatabase5, backupDatabase6), BackupTaskStatus.COMPLETED, null); + + Filter filter = new Filter(); + filter.setNamespace(List.of(namespace1, namespace2)); + filter.setMicroserviceName(List.of(microserviceName1, microserviceName4)); + filter.setDatabaseType(List.of(DatabaseType.POSTGRESQL, DatabaseType.CASSANDRA)); + filter.setDatabaseKind(List.of(DatabaseKind.TRANSACTIONAL)); + + Filter exclude = new Filter(); + exclude.setMicroserviceName(List.of(microserviceName4)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter)); + filterCriteria.setExclude(List.of(exclude)); + + List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + + assertEquals(1, filteredDatabases.size()); + + BackupDatabaseDelegate backupDatabaseDelegate = filteredDatabases.getFirst(); + + assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase1); + assertEquals(backupDatabaseDelegate.classifiers().getFirst(), backupDatabase1.getClassifiers().getFirst()); + } + + @Test + void getAllDbByFilter_RestorePart_2() { + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + String dbName4 = "db4"; + String dbName5 = "db5"; + String dbName6 = "db6"; + + String logicalBackupName1 = "lb1"; + String logicalBackupName2 = "db2"; + String logicalBackupName3 = "db3"; + + String adapterId1 = "adpater1"; + String adapterId2 = "adapter2"; + String adapterId3 = "adapter3"; + + String postgresqlType = "postgresql"; + String cassandraType = "cassandra"; + String arangoType = "arangodb"; + + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + String namespace4 = "namespace4"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + String microserviceName4 = "microserviceName4"; + String microserviceName5 = "microserviceName5"; + String microserviceName6 = "microserviceName6"; + + BackupDatabase backupDatabase1 = getBackupDatabase(dbName1, List.of(getClassifier(namespace1, microserviceName1, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase2 = getBackupDatabase(dbName2, List.of(getClassifier(namespace1, microserviceName2, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase3 = getBackupDatabase(dbName3, List.of(getClassifier(namespace2, microserviceName3, null)), true, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase4 = getBackupDatabase(dbName4, List.of(getClassifier(namespace2, microserviceName4, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase5 = getBackupDatabase(dbName5, List.of(getClassifier(namespace3, microserviceName5, null)), true, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase6 = getBackupDatabase(dbName6, List.of(getClassifier(namespace4, microserviceName6, null)), false, BackupTaskStatus.COMPLETED, null); + + LogicalBackup logicalBackup1 = getLogicalBackup(logicalBackupName1, adapterId1, postgresqlType, List.of(backupDatabase1, backupDatabase2), BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup2 = getLogicalBackup(logicalBackupName2, adapterId2, cassandraType, List.of(backupDatabase3, backupDatabase4), BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup3 = getLogicalBackup(logicalBackupName3, adapterId3, arangoType, List.of(backupDatabase5, backupDatabase6), BackupTaskStatus.COMPLETED, null); + + Filter filter1 = new Filter(); + filter1.setNamespace(List.of(namespace1)); + filter1.setDatabaseType(List.of(DatabaseType.POSTGRESQL, DatabaseType.CASSANDRA)); + filter1.setDatabaseKind(List.of(DatabaseKind.TRANSACTIONAL)); + + Filter filter2 = new Filter(); + filter2.setNamespace(List.of(namespace2)); + filter2.setMicroserviceName(List.of(microserviceName3)); + filter2.setDatabaseType(List.of(DatabaseType.POSTGRESQL, DatabaseType.CASSANDRA)); + + Filter exclude = new Filter(); + exclude.setDatabaseType(List.of(DatabaseType.POSTGRESQL)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter1, filter2)); + filterCriteria.setExclude(List.of(exclude)); + + List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + + assertEquals(1, filteredDatabases.size()); + + BackupDatabaseDelegate backupDatabaseDelegate = filteredDatabases.getFirst(); + + assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase3); + assertEquals(backupDatabaseDelegate.classifiers().getFirst(), backupDatabase3.getClassifiers().getFirst()); + } + + @Test + void getAllDbByFilter_RestorePart_3() { + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + + String logicalBackupName1 = "lb1"; + String logicalBackupName2 = "db2"; + + String adapterId1 = "adpater1"; + String adapterId2 = "adapter2"; + + String postgresqlType = "postgresql"; + String cassandraType = "cassandra"; + + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + + BackupDatabase backupDatabase1 = getBackupDatabase(dbName1, List.of(getClassifier(namespace1, microserviceName1, null), getClassifier(namespace2, microserviceName1, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase2 = getBackupDatabase(dbName2, List.of(getClassifier(namespace2, microserviceName2, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase3 = getBackupDatabase(dbName3, List.of(getClassifier(namespace3, microserviceName3, null)), true, BackupTaskStatus.COMPLETED, null); + + LogicalBackup logicalBackup1 = getLogicalBackup(logicalBackupName1, adapterId1, postgresqlType, List.of(backupDatabase1, backupDatabase2), BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup2 = getLogicalBackup(logicalBackupName2, adapterId2, cassandraType, List.of(backupDatabase3), BackupTaskStatus.COMPLETED, null); + + Filter filter1 = new Filter(); + filter1.setNamespace(List.of(namespace1)); + + Filter filter2 = new Filter(); + filter2.setNamespace(List.of(namespace2)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter1, filter2)); + + List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + + assertEquals(2, filteredDatabases.size()); + + BackupDatabaseDelegate backupDatabaseDelegate1 = filteredDatabases.stream() + .filter(db -> dbName1.equals(db.backupDatabase().getName())) + .findAny().orElse(null); + assertNotNull(backupDatabaseDelegate1); + assertEquals(backupDatabaseDelegate1.backupDatabase(), backupDatabase1); + assertEquals(2, backupDatabaseDelegate1.classifiers().size()); + + SortedMap classifier1 = backupDatabase1.getClassifiers().stream() + .filter(classifier -> namespace1.equals(classifier.get(NAMESPACE))) + .findAny().orElse(null); + SortedMap classifier2 = backupDatabase1.getClassifiers().stream() + .filter(classifier -> namespace2.equals(classifier.get(NAMESPACE))) + .findAny().orElse(null); + assertNotNull(classifier1); + assertNotNull(classifier2); + + BackupDatabaseDelegate backupDatabaseDelegate2 = filteredDatabases.stream() + .filter(db -> dbName2.equals(db.backupDatabase().getName())) + .findAny().orElse(null); + assertNotNull(backupDatabaseDelegate2); + assertEquals(backupDatabaseDelegate2.backupDatabase(), backupDatabase2); + assertEquals(1, backupDatabaseDelegate2.classifiers().size()); + assertEquals(backupDatabaseDelegate2.classifiers().getFirst(), backupDatabase2.getClassifiers().getFirst()); + } + + @Test + void getAllDbByFilter_RestorePart_4() { + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + + String logicalBackupName1 = "lb1"; + String logicalBackupName2 = "db2"; + + String adapterId1 = "adpater1"; + String adapterId2 = "adapter2"; + + String postgresqlType = "postgresql"; + String cassandraType = "cassandra"; + + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + + BackupDatabase backupDatabase1 = getBackupDatabase(dbName1, List.of(getClassifier(namespace1, microserviceName1, null), getClassifier(namespace2, microserviceName1, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase2 = getBackupDatabase(dbName2, List.of(getClassifier(namespace2, microserviceName2, null)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase3 = getBackupDatabase(dbName3, List.of(getClassifier(namespace3, microserviceName3, null)), true, BackupTaskStatus.COMPLETED, null); + + LogicalBackup logicalBackup1 = getLogicalBackup(logicalBackupName1, adapterId1, postgresqlType, List.of(backupDatabase1, backupDatabase2), BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup2 = getLogicalBackup(logicalBackupName2, adapterId2, cassandraType, List.of(backupDatabase3), BackupTaskStatus.COMPLETED, null); + + Filter filter1 = new Filter(); + filter1.setNamespace(List.of(namespace1)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter1)); + + List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + + assertEquals(1, filteredDatabases.size()); + + BackupDatabaseDelegate backupDatabaseDelegate1 = filteredDatabases.stream() + .filter(db -> dbName1.equals(db.backupDatabase().getName())) + .findAny().orElse(null); + assertNotNull(backupDatabaseDelegate1); + assertEquals(backupDatabaseDelegate1.backupDatabase(), backupDatabase1); + assertEquals(1, backupDatabaseDelegate1.classifiers().size()); + + SortedMap classifier1 = backupDatabase1.getClassifiers().stream() + .filter(classifier -> namespace1.equals(classifier.get(NAMESPACE))) + .findAny().orElse(null); + assertNotNull(classifier1); + } + @Test void validateAndFilterDatabasesForBackup_ExternalDatabaseStrategyInclude() { String namespace = "namespace"; @@ -2271,6 +2726,7 @@ void uploadBackupMetadata_restoreDeletedBackup_digestMismatch() { BackupResponse backupResponse = getBackupResponse(backupName, namespace); backupResponse.setDigest(anotherDigest); + IntegrityViolationException ex = assertThrows(IntegrityViolationException.class, () -> dbBackupV2Service.uploadBackupMetadata(backupResponse)); assertEquals( From f665ab3d66e084dea3b09ca51ff5a5cc7f946bf7 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Tue, 9 Dec 2025 15:54:09 +0500 Subject: [PATCH 03/46] feat: add NotEmptyFilter validation to Filter class --- .../dbaas/dto/backupV2/BackupRequest.java | 2 +- .../dbaas/dto/backupV2/BackupResponse.java | 2 +- .../cloud/dbaas/dto/backupV2/Filter.java | 4 ++ .../dbaas/dto/backupV2/FilterCriteria.java | 4 +- .../dbaas/dto/backupV2/RestoreRequest.java | 2 +- .../dbaas/dto/backupV2/RestoreResponse.java | 2 +- .../dbaas/service/DbBackupV2Service.java | 30 ++++++---- .../dbaas/utils/validation/BackupGroup.java | 4 -- .../utils/validation/NotEmptyFilter.java | 20 +++++++ .../validation/NotEmptyFilterValidation.java | 21 +++++++ .../dbaas/utils/validation/RestoreGroup.java | 4 -- .../utils/validation/group/BackupGroup.java | 4 ++ .../utils/validation/group/RestoreGroup.java | 4 ++ .../v3/DatabaseBackupV2ControllerTest.java | 59 +++++++++++++++++++ 14 files changed, 139 insertions(+), 23 deletions(-) delete mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/BackupGroup.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilter.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilterValidation.java delete mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/RestoreGroup.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/BackupGroup.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/RestoreGroup.java diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java index d825fb4b..ec6ec712 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java @@ -1,7 +1,7 @@ package com.netcracker.cloud.dbaas.dto.backupV2; import com.netcracker.cloud.dbaas.enums.ExternalDatabaseStrategy; -import com.netcracker.cloud.dbaas.utils.validation.BackupGroup; +import com.netcracker.cloud.dbaas.utils.validation.group.BackupGroup; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java index b555dfbf..448a35cd 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.netcracker.cloud.dbaas.enums.BackupStatus; import com.netcracker.cloud.dbaas.enums.ExternalDatabaseStrategy; -import com.netcracker.cloud.dbaas.utils.validation.BackupGroup; +import com.netcracker.cloud.dbaas.utils.validation.group.BackupGroup; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Filter.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Filter.java index 51eaf204..91811f18 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Filter.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Filter.java @@ -1,5 +1,8 @@ package com.netcracker.cloud.dbaas.dto.backupV2; +import com.netcracker.cloud.dbaas.utils.validation.NotEmptyFilter; +import com.netcracker.cloud.dbaas.utils.validation.group.BackupGroup; +import com.netcracker.cloud.dbaas.utils.validation.group.RestoreGroup; import lombok.Data; import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; @@ -9,6 +12,7 @@ import java.util.List; @Data +@NotEmptyFilter(groups = {BackupGroup.class, RestoreGroup.class}) @NoArgsConstructor @Schema(description = "Single filter criteria for backup and restore operations") public class Filter { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java index 3582bbb6..70144f08 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java @@ -1,6 +1,7 @@ package com.netcracker.cloud.dbaas.dto.backupV2; -import com.netcracker.cloud.dbaas.utils.validation.BackupGroup; +import com.netcracker.cloud.dbaas.utils.validation.group.BackupGroup; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; @@ -24,6 +25,7 @@ public class FilterCriteria { ) @NotNull(groups = {BackupGroup.class}) @Size(min = 1, groups = {BackupGroup.class}) + @Valid private List filter = new ArrayList<>(); @Schema( diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java index d44f4408..10863196 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java @@ -1,7 +1,7 @@ package com.netcracker.cloud.dbaas.dto.backupV2; import com.netcracker.cloud.dbaas.enums.ExternalDatabaseStrategy; -import com.netcracker.cloud.dbaas.utils.validation.RestoreGroup; +import com.netcracker.cloud.dbaas.utils.validation.group.RestoreGroup; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java index 2c656959..333b7e17 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java @@ -2,7 +2,7 @@ import com.netcracker.cloud.dbaas.enums.ExternalDatabaseStrategy; import com.netcracker.cloud.dbaas.enums.RestoreStatus; -import com.netcracker.cloud.dbaas.utils.validation.RestoreGroup; +import com.netcracker.cloud.dbaas.utils.validation.group.RestoreGroup; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 48e5ec4c..179d4a8a 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -42,6 +42,7 @@ import static com.netcracker.cloud.dbaas.Constants.*; import static com.netcracker.cloud.dbaas.entity.shared.AbstractDbState.DatabaseStateStatus.CREATED; +import static com.netcracker.cloud.dbaas.service.DBaaService.MARKED_FOR_DROP; import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP; @Slf4j @@ -430,22 +431,25 @@ protected Map> getAllDbByFilter(FilterCriteria .flatMap(exclude -> exclude.getDatabaseKind().stream()) .distinct() .count(); - if (uniqKinds == 2){ + if (uniqKinds == 2) { log.warn("No databases matching the filtering criteria were found during the backup"); throw new DbNotFoundException("No databases matching the filtering criteria were found during the backup", Source.builder().build()); } - log.info(filterCriteria.toString()); List filteredDatabases = databaseRegistryDbaasRepository .findAllDatabasesByFilter(filterCriteria.getFilter()) .stream() - .filter(registry -> filterCriteria.getExclude().stream().noneMatch(exclude -> { - boolean configurational = registry.getBgVersion() != null && !registry.getBgVersion().isBlank(); - return isMatches(exclude, - (String) registry.getClassifier().get(NAMESPACE), - (String) registry.getClassifier().get(MICROSERVICE_NAME), - registry.getType(), - configurational); - })) + .filter(registry -> { + if (!isValidRegistry(registry)) + return false; + return filterCriteria.getExclude().stream().noneMatch(exclude -> { + boolean configurational = registry.getBgVersion() != null && !registry.getBgVersion().isBlank(); + return isMatches(exclude, + (String) registry.getClassifier().get(NAMESPACE), + (String) registry.getClassifier().get(MICROSERVICE_NAME), + registry.getType(), + configurational); + }); + }) .toList(); if (filteredDatabases.isEmpty()) { @@ -457,6 +461,12 @@ protected Map> getAllDbByFilter(FilterCriteria .collect(Collectors.groupingBy(DatabaseRegistry::getDatabase)); } + private boolean isValidRegistry(DatabaseRegistry registry) { + return !registry.isMarkedForDrop() + && !registry.getClassifier().containsKey(MARKED_FOR_DROP) + && CREATED.equals(registry.getDbState().getDatabaseState()); + } + private boolean isMatches(Filter filter, String namespace, String microserviceName, String type, boolean configurational) { if (!filter.getNamespace().isEmpty() && !filter.getNamespace().contains(namespace)) { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/BackupGroup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/BackupGroup.java deleted file mode 100644 index 82a744a8..00000000 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/BackupGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.netcracker.cloud.dbaas.utils.validation; - -public interface BackupGroup { -} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilter.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilter.java new file mode 100644 index 00000000..8cf82b91 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilter.java @@ -0,0 +1,20 @@ +package com.netcracker.cloud.dbaas.utils.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = NotEmptyFilterValidation.class) +public @interface NotEmptyFilter { + String message() default "Filter must have at least one non-null field"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilterValidation.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilterValidation.java new file mode 100644 index 00000000..d41c3a34 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/NotEmptyFilterValidation.java @@ -0,0 +1,21 @@ +package com.netcracker.cloud.dbaas.utils.validation; + +import com.netcracker.cloud.dbaas.dto.backupV2.Filter; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.util.List; + +public class NotEmptyFilterValidation implements ConstraintValidator { + @Override + public boolean isValid(Filter value, ConstraintValidatorContext context) { + return isValid(value.getNamespace()) || + isValid(value.getMicroserviceName()) || + isValid(value.getDatabaseType()) || + isValid(value.getDatabaseKind()); + } + + private boolean isValid(List list) { + return list != null && !list.isEmpty(); + } +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/RestoreGroup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/RestoreGroup.java deleted file mode 100644 index 201d455e..00000000 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/RestoreGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.netcracker.cloud.dbaas.utils.validation; - -public interface RestoreGroup { -} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/BackupGroup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/BackupGroup.java new file mode 100644 index 00000000..b2c92397 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/BackupGroup.java @@ -0,0 +1,4 @@ +package com.netcracker.cloud.dbaas.utils.validation.group; + +public interface BackupGroup { +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/RestoreGroup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/RestoreGroup.java new file mode 100644 index 00000000..9c507d16 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/validation/group/RestoreGroup.java @@ -0,0 +1,4 @@ +package com.netcracker.cloud.dbaas.utils.validation.group; + +public interface RestoreGroup { +} diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java index e1925131..5fb38ef9 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java @@ -139,6 +139,49 @@ void initiateBackup_backupAlreadyExists() { verify(dbBackupV2Service, times(1)).backup(backupRequest, false); } + @Test + void initiateBackup_emptyFilterCase(){ + String namespace = "namespace"; + String backupName = "backupName"; + + BackupRequest backupRequest = createBackupRequest(namespace, backupName); + FilterCriteria emptyFilterCriteria = backupRequest.getFilterCriteria(); + emptyFilterCriteria.setFilter(List.of(new Filter())); + emptyFilterCriteria.setExclude(List.of(new Filter())); + + given().auth().preemptive().basic("backup_manager", "backup_manager") + .contentType(ContentType.JSON) + .body(backupRequest) + .when().post("/backup") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("reason", equalTo("Request does not contain required fields")) + .body("message", equalTo("filter[0]: Filter must have at least one non-null field")); + verify(dbBackupV2Service, times(0)).backup(backupRequest, false); + } + + @Test + void restoreBackup_emptyFilterCase(){ + String namespace = "namespace"; + String restoreName = "restoreName"; + String backupName = "backupName"; + + RestoreRequest restoreRequest = createRestoreRequest(namespace, restoreName); + FilterCriteria emptyFilterCriteria = restoreRequest.getFilterCriteria(); + emptyFilterCriteria.setFilter(List.of(new Filter())); + emptyFilterCriteria.setExclude(List.of(new Filter())); + + given().auth().preemptive().basic("backup_manager", "backup_manager") + .contentType(ContentType.JSON) + .body(restoreRequest) + .pathParam("backupName", backupName) + .when().post("/backup/{backupName}/restore") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("reason", equalTo("Request does not contain required fields")) + .body("message", equalTo("filter[0]: Filter must have at least one non-null field")); + verify(dbBackupV2Service, times(0)).restore(backupName, restoreRequest, false); + } @Test void getBackupStatus_validBackupNameCase() { String backupName = "test-backup-name"; @@ -330,6 +373,22 @@ public static BackupRequest createBackupRequest(String namespace, String backupN return dto; } + public static RestoreRequest createRestoreRequest(String namespace, String restoreName) { + Filter filter = new Filter(); + filter.setNamespace(List.of(namespace)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter)); + + RestoreRequest dto = new RestoreRequest(); + dto.setRestoreName(restoreName); + dto.setFilterCriteria(filterCriteria); + dto.setExternalDatabaseStrategy(ExternalDatabaseStrategy.FAIL); + dto.setBlobPath("path"); + dto.setStorageName("storageName"); + return dto; + } + private BackupResponse createBackupResponse(String backupName) { String storageName = "storageName"; SortedMap sortedMap = new TreeMap<>(); From af1004a606af5738709fbd2137f1b3d80353f348 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Tue, 9 Dec 2025 17:09:16 +0500 Subject: [PATCH 04/46] feat: expanded filtering for external databases --- .../dbaas/service/DbBackupV2Service.java | 50 ++++++++----------- .../dbaas/service/DbBackupV2ServiceTest.java | 41 +++++++++++++++ 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 179d4a8a..bfa0a0f3 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -814,7 +814,7 @@ protected Restore initializeFullRestoreStructure( return restore; } - private List validateAndFilterExternalDb(List externalDatabases, + protected List validateAndFilterExternalDb(List externalDatabases, ExternalDatabaseStrategy strategy, FilterCriteria filterCriteria) { if (externalDatabases == null || externalDatabases.isEmpty()) @@ -842,29 +842,27 @@ private List validateAndFilterExternalDb(List { + List> filteredClassifiers = db.getClassifiers().stream() + .filter(classifier -> { + String namespace = (String) classifier.get(NAMESPACE); + String microserviceName = (String) classifier.get(MICROSERVICE_NAME); + String type = db.getType(); + return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, false)) + && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); + }) + .map(c -> (SortedMap) new TreeMap<>(c)) + .toList(); + + if (filteredClassifiers.isEmpty()) { + return null; + } - if (filter.getNamespace().isEmpty()) { - if (!filter.getMicroserviceName().isEmpty()) { - throw new FunctionalityNotImplemented("restoration by microservice"); - } - if (!filter.getDatabaseKind().isEmpty()) { - throw new FunctionalityNotImplemented("restoration by databaseKind"); - } - if (!filter.getDatabaseType().isEmpty()) { - throw new FunctionalityNotImplemented("restoration by databaseType"); - } - throw new RequestValidationException(ErrorCodes.CORE_DBAAS_4043, "namespace", Source.builder().build()); - } - if (filter.getNamespace().size() > 1) { - throw new FunctionalityNotImplemented("restoration by several namespace"); - } - String namespace = filter.getNamespace().getFirst(); - yield mapper.toRestoreExternalDatabases(externalDatabases).stream() - .filter(db -> db.getClassifiers().stream() - .anyMatch(classifier -> - namespace.equals(classifier.get(NAMESPACE))) - ).toList(); + return mapper.toRestoreExternalDatabase(db); + }) + .filter(Objects::nonNull) + .toList(); } }; } @@ -1565,11 +1563,7 @@ private String extractErrorMessage(Throwable throwable) { } private boolean isFilterEmpty(FilterCriteria filterCriteria) { - if (filterCriteria == null || filterCriteria.getFilter() == null) - return true; - - return filterCriteria.getFilter().isEmpty() - || filterCriteria.getFilter().stream().allMatch(this::isSingleFilterEmpty); + return filterCriteria == null || filterCriteria.getFilter() == null || filterCriteria.getFilter().isEmpty(); } private boolean isSingleFilterEmpty(Filter f) { diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index 332b1c99..5493b347 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -2174,6 +2174,47 @@ void getAllDbByFilter_RestorePart_4() { assertNotNull(classifier1); } + @Test + void validateAndFilterExternalDb_testFiltering() { + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String namespace3 = "namespace3"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + + String dbName1 = "db1"; + String dbName2 = "db2"; + String dbName3 = "db3"; + + String postgresqlType = "postgresql"; + SortedMap classifier = getClassifier(namespace1, microserviceName1, null); + + BackupExternalDatabase externalDatabase1 = getBackupExternalDatabase(dbName1, postgresqlType, List.of(classifier)); + BackupExternalDatabase externalDatabase2 = getBackupExternalDatabase(dbName2, postgresqlType, List.of(getClassifier(namespace2, microserviceName2, null))); + BackupExternalDatabase externalDatabase3 = getBackupExternalDatabase(dbName3, postgresqlType, List.of(getClassifier(namespace3, microserviceName3, null))); + + Filter filter = new Filter(); + filter.setNamespace(List.of(namespace1, namespace2)); + + Filter exclude = new Filter(); + exclude.setMicroserviceName(List.of(microserviceName2)); + + FilterCriteria filterCriteria = new FilterCriteria(); + filterCriteria.setFilter(List.of(filter)); + filterCriteria.setExclude(List.of(exclude)); + + List restoreExternalDatabases = dbBackupV2Service.validateAndFilterExternalDb(List.of(externalDatabase1, externalDatabase2, externalDatabase3), ExternalDatabaseStrategy.INCLUDE, filterCriteria); + assertEquals(1, restoreExternalDatabases.size()); + + RestoreExternalDatabase externalDb = restoreExternalDatabases.getFirst(); + assertEquals(dbName1, externalDb.getName()); + assertEquals(postgresqlType, externalDb.getType()); + assertEquals(1, externalDb.getClassifiers().size()); + assertEquals(classifier, externalDb.getClassifiers().getFirst()); + } + @Test void validateAndFilterDatabasesForBackup_ExternalDatabaseStrategyInclude() { String namespace = "namespace"; From 5563e442bbb3daabf9a91226fcb2f9b966804cb9 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 15 Dec 2025 11:17:04 +0500 Subject: [PATCH 05/46] feat: add dryRun --- .../cloud/dbaas/dto/backupV2/Classifier.java | 16 ++ .../dbaas/dto/backupV2/ClassifierType.java | 5 + .../dto/backupV2/RestoreDatabaseResponse.java | 2 +- .../RestoreExternalDatabaseResponse.java | 2 +- .../dto/backupV2/BackupDatabaseDelegate.java | 3 +- .../dto/backupV2/BackupExternalDelegate.java | 9 ++ .../entity/pg/backupV2/RestoreDatabase.java | 3 +- .../pg/backupV2/RestoreExternalDatabase.java | 3 +- .../cloud/dbaas/mapper/BackupV2Mapper.java | 6 +- .../dbaas/service/DbBackupV2Service.java | 138 +++++++++++------- 10 files changed, 125 insertions(+), 62 deletions(-) create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierType.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java new file mode 100644 index 00000000..a83273d1 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java @@ -0,0 +1,16 @@ +package com.netcracker.cloud.dbaas.dto.backupV2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.SortedMap; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Classifier { + private ClassifierType type; + private SortedMap classifier; + private SortedMap classifierBeforeMapper; +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierType.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierType.java new file mode 100644 index 00000000..c52e73bb --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierType.java @@ -0,0 +1,5 @@ +package com.netcracker.cloud.dbaas.dto.backupV2; + +public enum ClassifierType { + NEW, REPLACED, TRANSIENT_REPLACED +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java index c054648c..5a7615c7 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java @@ -26,7 +26,7 @@ public class RestoreDatabaseResponse { description = "List of database classifiers. Each classifier is a sorted map of attributes.", examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]" ) - private List> classifiers; + private List classifiers; @Schema( description = "List of database users", examples = "[{\"name\":\"username\",\"role\":\"admin\"}" diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index a06ba0e4..2350462d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -19,6 +19,6 @@ public class RestoreExternalDatabaseResponse { description = "List of database classifiers. Each classifier is a sorted map of attributes.", examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]" ) - private List> classifiers; + private List classifiers; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java index a30e6453..110c72d9 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java @@ -1,5 +1,6 @@ package com.netcracker.cloud.dbaas.entity.dto.backupV2; +import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; import com.netcracker.cloud.dbaas.entity.pg.backupV2.BackupDatabase; import java.util.List; @@ -7,4 +8,4 @@ public record BackupDatabaseDelegate(BackupDatabase backupDatabase, - List> classifiers) {} + List classifiers) {} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java new file mode 100644 index 00000000..3d981826 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java @@ -0,0 +1,9 @@ +package com.netcracker.cloud.dbaas.entity.dto.backupV2; + +import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; +import com.netcracker.cloud.dbaas.entity.pg.backupV2.BackupExternalDatabase; + +import java.util.List; + +public record BackupExternalDelegate(BackupExternalDatabase backupExternalDatabase, List classifiers) { +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java index ab5356d8..0cbf4eb9 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java @@ -1,5 +1,6 @@ package com.netcracker.cloud.dbaas.entity.pg.backupV2; +import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; import com.netcracker.cloud.dbaas.enums.RestoreTaskStatus; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; @@ -41,7 +42,7 @@ public class RestoreDatabase { @NotNull @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") - private List> classifiers; + private List classifiers; @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java index 2295d3ca..eb10429b 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java @@ -1,6 +1,7 @@ package com.netcracker.cloud.dbaas.entity.pg.backupV2; import com.fasterxml.jackson.annotation.JsonBackReference; +import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -40,5 +41,5 @@ public class RestoreExternalDatabase { @NotNull @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") - private List> classifiers; + private List classifiers; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java index a6375838..8a58d807 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java @@ -2,6 +2,7 @@ import com.netcracker.cloud.dbaas.dto.Source; import com.netcracker.cloud.dbaas.dto.backupV2.*; +import com.netcracker.cloud.dbaas.entity.dto.backupV2.BackupExternalDelegate; import com.netcracker.cloud.dbaas.entity.pg.backupV2.*; import com.netcracker.cloud.dbaas.enums.BackupTaskStatus; import com.netcracker.cloud.dbaas.enums.RestoreTaskStatus; @@ -100,7 +101,8 @@ private static , R extends Enum> R mapStatus( } @Mapping(target = "id", ignore = true) - RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDatabase backupExternalDatabase); + @Mapping(target = "classifiers", source = "classifiers") + RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDelegate backupExternalDelegate); - List toRestoreExternalDatabases(List backupExternalDatabases); + List toRestoreExternalDatabases(List backupExternalDelegates); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index bfa0a0f3..eb0b8cd2 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -644,9 +644,6 @@ private void finalizeBackupDeletion(Backup backup, Map failedAda } public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, boolean dryRun) { - if (dryRun) - throw new FunctionalityNotImplemented("dryRun"); - String restoreName = restoreRequest.getRestoreName(); if (restoreRepository.findByIdOptional(restoreName).isPresent()) { log.error("Restore with name {} already exists", restoreName); @@ -684,7 +681,8 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, throw new IllegalResourceStateException("another restore is being processed", Source.builder().build()); Restore restore = initializeFullRestoreStructure(backup, restoreRequest); - restoreRepository.save(restore); + if (!dryRun) + restoreRepository.save(restore); // unlock method after save restore lock.unlock(); unlocked = true; @@ -692,13 +690,14 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, // DryRun on adapters startRestore(restore, true); aggregateRestoreStatus(restore); - if (RestoreStatus.FAILED != restore.getStatus()) { + if (!dryRun && RestoreStatus.FAILED != restore.getStatus()) { // Real run on adapters restore = getRestoreOrThrowException(restoreName); startRestore(restore, false); aggregateRestoreStatus(restore); } - restoreRepository.save(restore); + if (!dryRun) + restoreRepository.save(restore); return mapper.toRestoreResponse(restore); } finally { if (!unlocked) { @@ -709,12 +708,20 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, protected List getAllDbByFilter(List backupDatabasesToFilter, FilterCriteria filterCriteria) { if (isFilterEmpty(filterCriteria)) - return backupDatabasesToFilter.stream().map(db -> new BackupDatabaseDelegate(db, db.getClassifiers())) + return backupDatabasesToFilter.stream() + .map(db -> + new BackupDatabaseDelegate( + db, + db.getClassifiers().stream() + .map(c -> new Classifier(ClassifierType.NEW, null, c)) + .toList() + ) + ) .toList(); List databaseDelegateList = backupDatabasesToFilter.stream() .map(db -> { - List> filteredClassifiers = db.getClassifiers().stream() + List filteredClassifiers = db.getClassifiers().stream() .filter(classifier -> { String namespace = (String) classifier.get(NAMESPACE); String microserviceName = (String) classifier.get(MICROSERVICE_NAME); @@ -723,7 +730,7 @@ protected List getAllDbByFilter(List bac return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) - .map(c -> (SortedMap) new TreeMap<>(c)) + .map(c -> new Classifier(ClassifierType.NEW, null, c)) .toList(); if (filteredClassifiers.isEmpty()) { @@ -815,8 +822,8 @@ protected Restore initializeFullRestoreStructure( } protected List validateAndFilterExternalDb(List externalDatabases, - ExternalDatabaseStrategy strategy, - FilterCriteria filterCriteria) { + ExternalDatabaseStrategy strategy, + FilterCriteria filterCriteria) { if (externalDatabases == null || externalDatabases.isEmpty()) return List.of(); @@ -840,11 +847,22 @@ protected List validateAndFilterExternalDb(List { log.info("Including external databases to restore by strategy: {}", ExternalDatabaseStrategy.INCLUDE); if (isFilterEmpty(filterCriteria)) - yield mapper.toRestoreExternalDatabases(externalDatabases); + yield mapper.toRestoreExternalDatabases( + externalDatabases.stream() + .map(db -> + new BackupExternalDelegate(db, db.getClassifiers().stream() + .map(c -> + new Classifier(ClassifierType.NEW, null, c) + ) + .toList() + ) + ) + .toList() + ); yield externalDatabases.stream() .map(db -> { - List> filteredClassifiers = db.getClassifiers().stream() + List filteredClassifiers = db.getClassifiers().stream() .filter(classifier -> { String namespace = (String) classifier.get(NAMESPACE); String microserviceName = (String) classifier.get(MICROSERVICE_NAME); @@ -852,14 +870,14 @@ protected List validateAndFilterExternalDb(List isMatches(filter, namespace, microserviceName, type, false)) && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); }) - .map(c -> (SortedMap) new TreeMap<>(c)) + .map(c -> new Classifier(ClassifierType.NEW, null, c)) .toList(); if (filteredClassifiers.isEmpty()) { return null; } - return mapper.toRestoreExternalDatabase(db); + return mapper.toRestoreExternalDatabase(new BackupExternalDelegate(db, filteredClassifiers)); }) .filter(Objects::nonNull) .toList(); @@ -872,7 +890,7 @@ private List executeMappingForExternalDb(List { Set> uniqueClassifiers = new HashSet<>(); - List> updatedClassifiers = db.getClassifiers().stream() + List updatedClassifiers = db.getClassifiers().stream() .map(classifier -> updateAndValidateClassifier(classifier, mapping, uniqueClassifiers)) .toList(); db.setClassifiers(updatedClassifiers); @@ -886,8 +904,8 @@ private List createRestoreDatabases( return backupDatabases.stream() .map(delegatedBackupDatabase -> { BackupDatabase backupDatabase = delegatedBackupDatabase.backupDatabase(); - List> classifiers = delegatedBackupDatabase.classifiers(); - String namespace = (String) classifiers.getFirst().get(NAMESPACE); + List classifiers = delegatedBackupDatabase.classifiers(); + String namespace = classifiers.getFirst().getClassifier() != null ? (String) classifiers.getFirst().getClassifier().get(NAMESPACE) : (String) classifiers.getFirst().getClassifierBeforeMapper().get(NAMESPACE); String bgVersion = null; if (backupDatabase.isConfigurational()) { Optional bgNamespace = bgNamespaceRepository.findBgNamespaceByNamespace(namespace); @@ -911,15 +929,16 @@ private List createRestoreDatabases( .toList(); } - private SortedMap updateClassifier(SortedMap classifier, Mapping mapping) { - String targetNamespace = getValue(mapping.getNamespaces(), (String) classifier.get(NAMESPACE)); - String targetTenant = getValue(mapping.getTenants(), (String) classifier.get(TENANT_ID)); + private Classifier updateClassifier(Classifier classifier, Mapping mapping) { + String targetNamespace = getValue(mapping.getNamespaces(), (String) classifier.getClassifierBeforeMapper().get(NAMESPACE)); + String targetTenant = getValue(mapping.getTenants(), (String) classifier.getClassifierBeforeMapper().get(TENANT_ID)); - SortedMap updatedClassifier = new TreeMap<>(classifier); + SortedMap updatedClassifier = new TreeMap<>(classifier.getClassifierBeforeMapper()); updatedClassifier.put(NAMESPACE, targetNamespace); if (targetTenant != null) updatedClassifier.put(TENANT_ID, targetTenant); - return updatedClassifier; + classifier.setClassifier(updatedClassifier); + return classifier; } private String getValue(Map map, String oldValue) { @@ -932,39 +951,35 @@ private String getValue(Map map, String oldValue) { private Map> groupBackupDatabasesByTypeAndAdapter( List backupDatabases, Mapping mapping) { - + Set> uniqueClassifiers = new HashSet<>(); return backupDatabases.stream() - .map(db -> mapToPhysicalDatabaseEntry(db, mapping)) - .filter(Objects::nonNull) + .map(db -> mapToPhysicalDatabaseEntry(db, mapping, uniqueClassifiers)) .collect(Collectors.groupingBy( Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList()) )); } - private Map.Entry mapToPhysicalDatabaseEntry( - BackupDatabaseDelegate db, Mapping mapping) { - List> classifiers = db.classifiers(); - if (classifiers.isEmpty()) { - return null; - } - - SortedMap firstClassifier = classifiers.getFirst(); + private Map.Entry mapToPhysicalDatabaseEntry(BackupDatabaseDelegate db, + Mapping mapping, + Set> uniqueClassifiers) { + List classifiers = db.classifiers(); + SortedMap firstClassifier = classifiers.getFirst().getClassifierBeforeMapper(); String targetNamespace = (String) firstClassifier.get(NAMESPACE); String microserviceName = (String) firstClassifier.get(MICROSERVICE_NAME); // Mapping classifiers if (mapping != null && mapping.getNamespaces() != null) { - Set> uniqueClassifiers = new HashSet<>(); classifiers = db.classifiers().stream() .map(classifier -> updateAndValidateClassifier(classifier, mapping, uniqueClassifiers)) .toList(); // Find the first classifier whose namespace exists in the mapping SortedMap matchedClassifier = classifiers.stream() + .map(Classifier::getClassifierBeforeMapper) .filter(c -> mapping.getNamespaces().containsKey((String) c.get(NAMESPACE))) .findFirst() - .orElse(classifiers.getFirst()); + .orElse(classifiers.getFirst().getClassifierBeforeMapper()); String oldNamespace = (String) matchedClassifier.get(NAMESPACE); targetNamespace = mapping.getNamespaces().getOrDefault(oldNamespace, oldNamespace); @@ -987,17 +1002,17 @@ private Map.Entry mapToPhysicalDatabas return Map.entry(physicalDatabase, new BackupDatabaseDelegate(db.backupDatabase(), classifiers)); } - private SortedMap updateAndValidateClassifier( - SortedMap classifier, + private Classifier updateAndValidateClassifier( + Classifier classifier, Mapping mapping, Set> uniqueClassifiers) { - SortedMap updatedClassifier = updateClassifier(classifier, mapping); + Classifier updatedClassifier = updateClassifier(classifier, mapping); // To prevent collision during mapping - if (!uniqueClassifiers.add(updatedClassifier)) { + if (!uniqueClassifiers.add(updatedClassifier.getClassifier())) { String msg = String.format( - "Duplicate classifier detected after mapping: classifier='%s', mapping='%s'. " + + "Duplicate classifier detected after mapping: classifier='%s', classifierBeforeMapping='%s', mapping='%s'. " + "Ensure all classifiers remain unique after mapping.", - classifier, mapping); + classifier.getClassifier(), classifier.getClassifierBeforeMapper(), mapping); log.error(msg); throw new IllegalResourceStateException(msg, Source.builder().build()); } @@ -1089,12 +1104,12 @@ private List> buildRestoreDatabases(LogicalRestore logicalRe return logicalRestore.getRestoreDatabases().stream() .map(restoreDatabase -> { String namespace = restoreDatabase.getClassifiers().stream() - .map(i -> (String) i.get(NAMESPACE)) + .map(c -> (String) c.getClassifier().get(NAMESPACE)) .findFirst() .orElse(""); String microserviceName = restoreDatabase.getClassifiers().stream() - .map(i -> (String) i.get(MICROSERVICE_NAME)) + .map(c -> (String) c.getClassifier().get(MICROSERVICE_NAME)) .findFirst() .orElse(""); @@ -1281,7 +1296,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { String type = logicalRestore.getType(); - Set> classifiers = new HashSet<>(); + Set classifiers = new HashSet<>(); log.info("Processing restoreDatabase={}", restoreDatabase.getName()); findSimilarDbByClassifier(classifiers, restoreDatabase.getClassifiers(), type); @@ -1291,7 +1306,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { log.info("Processing externalDatabase={}, type={}", externalDatabase.getName(), externalDatabase.getType()); String type = externalDatabase.getType(); - Set> classifiers = new HashSet<>(); + Set classifiers = new HashSet<>(); findSimilarDbByClassifier(classifiers, externalDatabase.getClassifiers(), type); Database newDatabase = createLogicalDatabase( externalDatabase.getName(), null, - classifiers, + classifiers.stream().map(Classifier::getClassifier).collect(Collectors.toSet()), type, true, true, @@ -1337,20 +1353,28 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map> uniqueClassifiers, - List> classifiers, + private void findSimilarDbByClassifier(Set uniqueClassifiers, + List classifiers, String type) { classifiers.forEach(classifier -> { - uniqueClassifiers.add(new TreeMap<>(classifier)); - log.debug("Classifier candidate: {}", classifier); + uniqueClassifiers.add(classifier); + SortedMap currClassifier = classifier.getClassifier(); + log.debug("Classifier candidate: {}", currClassifier); databaseRegistryDbaasRepository - .getDatabaseByClassifierAndType(classifier, type) + .getDatabaseByClassifierAndType(currClassifier, type) .ifPresent(dbRegistry -> { + classifier.setType(ClassifierType.REPLACED); Database db = dbRegistry.getDatabase(); - log.info("Found existing database {} for classifier {}", db.getId(), classifier); - List> existClassifiers = db.getDatabaseRegistry().stream() + log.info("Found existing database {} for classifier {}", db.getId(), currClassifier); + List existClassifiers = db.getDatabaseRegistry().stream() .map(AbstractDatabaseRegistry::getClassifier) .map(TreeMap::new) + .map(c -> { + if (currClassifier.equals(c)) + return wrapClassifier(ClassifierType.REPLACED, c, classifier.getClassifierBeforeMapper()); + else + return wrapClassifier(ClassifierType.TRANSIENT_REPLACED, c, null); + }) .toList(); uniqueClassifiers.addAll(existClassifiers); @@ -1361,6 +1385,10 @@ private void findSimilarDbByClassifier(Set> uniqueClas }); } + private Classifier wrapClassifier(ClassifierType type, SortedMap classifier, SortedMap classifierBeforeMapper) { + return new Classifier(type, classifier, classifierBeforeMapper); + } + private Database createLogicalDatabase(String dbName, Map settings, Set> classifiers, From 31e33e75b9762d4466c163d34fc422278d9b6d7f Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 18 Dec 2025 11:09:03 +0500 Subject: [PATCH 06/46] feat: change restore classifier variable --- .../dbaas/converter/ClassifierConverter.java | 13 +-- .../cloud/dbaas/dto/backupV2/Classifier.java | 12 +++ .../cloud/dbaas/mapper/BackupV2Mapper.java | 2 + .../dbaas/service/DbBackupV2Service.java | 93 +++++++++++-------- .../dbaas/service/DbBackupV2ServiceTest.java | 31 ++++--- 5 files changed, 92 insertions(+), 59 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java index db48c193..a14e7cfc 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java @@ -3,17 +3,17 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; - +import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; import jakarta.persistence.AttributeConverter; + import java.io.IOException; -import java.util.SortedMap; -public class ClassifierConverter implements AttributeConverter, String> { +public class ClassifierConverter implements AttributeConverter { private ObjectMapper objectMapper = new ObjectMapper(); @Override - public String convertToDatabaseColumn(SortedMap attribute) { + public String convertToDatabaseColumn(Classifier attribute) { try { return objectMapper.writeValueAsString(attribute); } catch (JsonProcessingException e) { @@ -22,9 +22,10 @@ public String convertToDatabaseColumn(SortedMap attribute) { } @Override - public SortedMap convertToEntityAttribute(String dbData) { + public Classifier convertToEntityAttribute(String dbData) { try { - return objectMapper.readValue(dbData, new TypeReference>() {}); + return objectMapper.readValue(dbData, new TypeReference<>() { + }); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java index a83273d1..f4b8f345 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java @@ -4,6 +4,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.Objects; import java.util.SortedMap; @Data @@ -13,4 +14,15 @@ public class Classifier { private ClassifierType type; private SortedMap classifier; private SortedMap classifierBeforeMapper; + + @Override + public boolean equals(Object o) { + if (!(o instanceof Classifier that)) return false; + return type == that.type && Objects.equals(classifier, that.classifier) && Objects.equals(classifierBeforeMapper, that.classifierBeforeMapper); + } + + @Override + public int hashCode() { + return Objects.hash(type, classifier, classifierBeforeMapper); + } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java index 8a58d807..481e1a69 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java @@ -101,6 +101,8 @@ private static , R extends Enum> R mapStatus( } @Mapping(target = "id", ignore = true) + @Mapping(target = "name", source = "backupExternalDatabase.name") + @Mapping(target = "type", source = "backupExternalDatabase.type") @Mapping(target = "classifiers", source = "classifiers") RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDelegate backupExternalDelegate); diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index eb0b8cd2..580eb5a7 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -698,6 +698,19 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, } if (!dryRun) restoreRepository.save(restore); + else { + restore.getLogicalRestores().forEach(lr -> { + for (RestoreDatabase restoreDatabase : lr.getRestoreDatabases()) { + restoreDatabase.setClassifiers( + findSimilarDbByClassifier(restoreDatabase.getClassifiers(), lr.getType()).stream() + .toList() + ); + } + }); + restore.getExternalDatabases().forEach(externalDb -> { + externalDb.setClassifiers(findSimilarDbByClassifier(externalDb.getClassifiers(), externalDb.getType()).stream().toList()); + }); + } return mapper.toRestoreResponse(restore); } finally { if (!unlocked) { @@ -815,8 +828,8 @@ protected Restore initializeFullRestoreStructure( .mapToInt(lr -> lr.getRestoreDatabases().size()) .sum(); - log.info("Restore structure initialized: restoreName={}, logicalRestores={}, restoreDatabases={}", - restore.getName(), logicalRestores.size(), totalDatabases); + log.info("Restore structure initialized: restoreName={}, logicalRestores={}, restoreDatabases={}, externalDatabases={}", + restore.getName(), logicalRestores.size(), totalDatabases, externalDatabases.size()); return restore; } @@ -1010,9 +1023,9 @@ private Classifier updateAndValidateClassifier( // To prevent collision during mapping if (!uniqueClassifiers.add(updatedClassifier.getClassifier())) { String msg = String.format( - "Duplicate classifier detected after mapping: classifier='%s', classifierBeforeMapping='%s', mapping='%s'. " + + "Duplicate classifier detected after mapping: classifier='%s', classifierBeforeMapping='%s'. " + "Ensure all classifiers remain unique after mapping.", - classifier.getClassifier(), classifier.getClassifierBeforeMapper(), mapping); + classifier.getClassifier(), classifier.getClassifierBeforeMapper()); log.error(msg); throw new IllegalResourceStateException(msg, Source.builder().build()); } @@ -1104,12 +1117,12 @@ private List> buildRestoreDatabases(LogicalRestore logicalRe return logicalRestore.getRestoreDatabases().stream() .map(restoreDatabase -> { String namespace = restoreDatabase.getClassifiers().stream() - .map(c -> (String) c.getClassifier().get(NAMESPACE)) + .map(c -> c.getClassifier() != null ? (String) c.getClassifier().get(NAMESPACE) : (String) c.getClassifierBeforeMapper().get(NAMESPACE)) .findFirst() .orElse(""); String microserviceName = restoreDatabase.getClassifiers().stream() - .map(c -> (String) c.getClassifier().get(MICROSERVICE_NAME)) + .map(c -> c.getClassifier() != null ? (String) c.getClassifier().get(MICROSERVICE_NAME) : (String) c.getClassifierBeforeMapper().get(MICROSERVICE_NAME)) .findFirst() .orElse(""); @@ -1296,17 +1309,15 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { String type = logicalRestore.getType(); - Set classifiers = new HashSet<>(); - log.info("Processing restoreDatabase={}", restoreDatabase.getName()); - findSimilarDbByClassifier(classifiers, restoreDatabase.getClassifiers(), type); + Set classifiers = findSimilarDbByClassifier(restoreDatabase.getClassifiers(), type); String adapterId = logicalRestore.getAdapterId(); String physicalDatabaseId = physicalDatabasesService.getByAdapterId(adapterId).getPhysicalDatabaseIdentifier(); List ensuredUsers = dbNameToEnsuredUsers.get(restoreDatabase.getName()); Database newDatabase = createLogicalDatabase( restoreDatabase.getName(), restoreDatabase.getSettings(), - classifiers.stream().map(Classifier::getClassifier).collect(Collectors.toSet()), + classifiers.stream().map(c -> c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), type, false, false, @@ -1327,9 +1338,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { log.info("Processing externalDatabase={}, type={}", externalDatabase.getName(), externalDatabase.getType()); String type = externalDatabase.getType(); - Set classifiers = new HashSet<>(); - - findSimilarDbByClassifier(classifiers, externalDatabase.getClassifiers(), type); + Set classifiers = findSimilarDbByClassifier(externalDatabase.getClassifiers(), type); Database newDatabase = createLogicalDatabase( externalDatabase.getName(), null, @@ -1341,6 +1350,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map uniqueClassifiers, - List classifiers, - String type) { + private Set findSimilarDbByClassifier(List classifiers, + String type) { + Set uniqueClassifiers = new HashSet<>(); classifiers.forEach(classifier -> { - uniqueClassifiers.add(classifier); - SortedMap currClassifier = classifier.getClassifier(); + SortedMap currClassifier = classifier.getClassifier() != null ? classifier.getClassifier() + : classifier.getClassifierBeforeMapper(); log.debug("Classifier candidate: {}", currClassifier); - databaseRegistryDbaasRepository - .getDatabaseByClassifierAndType(currClassifier, type) - .ifPresent(dbRegistry -> { - classifier.setType(ClassifierType.REPLACED); - Database db = dbRegistry.getDatabase(); - log.info("Found existing database {} for classifier {}", db.getId(), currClassifier); - List existClassifiers = db.getDatabaseRegistry().stream() - .map(AbstractDatabaseRegistry::getClassifier) - .map(TreeMap::new) - .map(c -> { - if (currClassifier.equals(c)) - return wrapClassifier(ClassifierType.REPLACED, c, classifier.getClassifierBeforeMapper()); - else - return wrapClassifier(ClassifierType.TRANSIENT_REPLACED, c, null); - }) - .toList(); - uniqueClassifiers.addAll(existClassifiers); - dBaaService.markDatabasesAsOrphan(dbRegistry); - log.info("Database {} marked as orphan", db.getId()); - databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); - }); + Optional optionalDatabaseRegistry = databaseRegistryDbaasRepository + .getDatabaseByClassifierAndType(currClassifier, type); + + if (optionalDatabaseRegistry.isPresent()) { + DatabaseRegistry dbRegistry = optionalDatabaseRegistry.get(); + classifier.setType(ClassifierType.REPLACED); + Database db = dbRegistry.getDatabase(); + log.info("Found existing database {} for classifier {}", db.getId(), currClassifier); + Set existClassifiers = db.getDatabaseRegistry().stream() + .map(AbstractDatabaseRegistry::getClassifier) + .map(TreeMap::new) + .map(c -> { + if (currClassifier.equals(c)) + return classifier; + else + return wrapClassifier(ClassifierType.TRANSIENT_REPLACED, c, null); + }) + .collect(Collectors.toSet()); + + uniqueClassifiers.addAll(existClassifiers); + dBaaService.markDatabasesAsOrphan(dbRegistry); + log.info("Database {} marked as orphan", db.getId()); + databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); + } else + uniqueClassifiers.add(classifier); }); + return uniqueClassifiers; } private Classifier wrapClassifier(ClassifierType type, SortedMap classifier, SortedMap classifierBeforeMapper) { diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index c4f5910f..b5164798 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -930,7 +930,7 @@ void restore_withoutMapping_finishedWithStatusCompleted() { assertEquals(1, restoreDatabase1.getClassifiers().size()); assertEquals(1, restoreDatabase1.getDuration()); - SortedMap classifier = restoreDatabase1.getClassifiers().getFirst(); + SortedMap classifier = restoreDatabase1.getClassifiers().getFirst().getClassifierBeforeMapper(); assertTrue( namespace.equals(classifier.get(NAMESPACE)) && microserviceName1.equals(classifier.get(MICROSERVICE_NAME)) @@ -955,7 +955,7 @@ void restore_withoutMapping_finishedWithStatusCompleted() { assertEquals(1, restoreDatabase2.getClassifiers().size()); assertEquals(1, restoreDatabase2.getDuration()); - classifier = restoreDatabase2.getClassifiers().getFirst(); + classifier = restoreDatabase2.getClassifiers().getFirst().getClassifierBeforeMapper(); assertTrue( namespace.equals(classifier.get(NAMESPACE)) && microserviceName2.equals(classifier.get(MICROSERVICE_NAME)) @@ -1196,7 +1196,7 @@ void restore_withMapping_finishedWithStatusCompleted() { assertEquals(externalDbName, restoreExternalDatabase.getName()); assertEquals(postgresqlType, restoreExternalDatabase.getType()); - SortedMap externalClassifier = restoreExternalDatabase.getClassifiers().getFirst(); + SortedMap externalClassifier = restoreExternalDatabase.getClassifiers().getFirst().getClassifier(); assertTrue( mappedNamespace.equals(externalClassifier.get(NAMESPACE)) && microserviceName3.equals(externalClassifier.get(MICROSERVICE_NAME)) && @@ -1222,7 +1222,7 @@ void restore_withMapping_finishedWithStatusCompleted() { assertEquals(1, restoreDatabase1.getClassifiers().size()); assertEquals(1, restoreDatabase1.getDuration()); - SortedMap classifier = restoreDatabase1.getClassifiers().getFirst(); + SortedMap classifier = restoreDatabase1.getClassifiers().getFirst().getClassifier(); assertTrue( mappedNamespace.equals(classifier.get(NAMESPACE)) && microserviceName1.equals(classifier.get(MICROSERVICE_NAME)) && @@ -1248,7 +1248,7 @@ void restore_withMapping_finishedWithStatusCompleted() { assertEquals(1, restoreDatabase2.getClassifiers().size()); assertEquals(1, restoreDatabase2.getDuration()); - classifier = restoreDatabase2.getClassifiers().getFirst(); + classifier = restoreDatabase2.getClassifiers().getFirst().getClassifier(); assertTrue( mappedNamespace.equals(classifier.get(NAMESPACE)) && microserviceName2.equals(classifier.get(MICROSERVICE_NAME)) && @@ -1345,11 +1345,11 @@ void restore_mappingInvokeCollision() { SortedMap classifier1 = getClassifier(namespace, microserviceName, tenantId); SortedMap classifier2 = getClassifier(namespace, microserviceName, anotherTenantId); - + SortedMap mappedClassifier = getClassifier(mappedNamespace, microserviceName, anotherTenantId); String msg = String.format( - "Resource has illegal state: Duplicate classifier detected after mapping: classifier='%s', mapping='%s'. " + + "Resource has illegal state: Duplicate classifier detected after mapping: classifier='%s', classifierBeforeMapping='%s'. " + "Ensure all classifiers remain unique after mapping.", - classifier2, mapping); + mappedClassifier, classifier2); BackupDatabase backupDatabase = getBackupDatabase(dbName, List.of(classifier1, classifier2), false, BackupTaskStatus.COMPLETED, ""); LogicalBackup logicalBackup = getLogicalBackup(logicalBackupName, adapterId, postgresType, List.of(backupDatabase), BackupTaskStatus.COMPLETED, ""); @@ -1978,7 +1978,7 @@ void getAllDbByFilter_RestorePart_1() { BackupDatabaseDelegate backupDatabaseDelegate = filteredDatabases.getFirst(); assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase1); - assertEquals(backupDatabaseDelegate.classifiers().getFirst(), backupDatabase1.getClassifiers().getFirst()); + assertEquals(backupDatabaseDelegate.classifiers().getFirst().getClassifierBeforeMapper(), backupDatabase1.getClassifiers().getFirst()); } @Test @@ -2050,7 +2050,7 @@ void getAllDbByFilter_RestorePart_2() { BackupDatabaseDelegate backupDatabaseDelegate = filteredDatabases.getFirst(); assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase3); - assertEquals(backupDatabaseDelegate.classifiers().getFirst(), backupDatabase3.getClassifiers().getFirst()); + assertEquals(backupDatabaseDelegate.classifiers().getFirst().getClassifierBeforeMapper(), backupDatabase3.getClassifiers().getFirst()); } @Test @@ -2119,7 +2119,7 @@ void getAllDbByFilter_RestorePart_3() { assertNotNull(backupDatabaseDelegate2); assertEquals(backupDatabaseDelegate2.backupDatabase(), backupDatabase2); assertEquals(1, backupDatabaseDelegate2.classifiers().size()); - assertEquals(backupDatabaseDelegate2.classifiers().getFirst(), backupDatabase2.getClassifiers().getFirst()); + assertEquals(backupDatabaseDelegate2.classifiers().getFirst().getClassifierBeforeMapper(), backupDatabase2.getClassifiers().getFirst()); } @Test @@ -2214,7 +2214,7 @@ void validateAndFilterExternalDb_testFiltering() { assertEquals(dbName1, externalDb.getName()); assertEquals(postgresqlType, externalDb.getType()); assertEquals(1, externalDb.getClassifiers().size()); - assertEquals(classifier, externalDb.getClassifiers().getFirst()); + assertEquals(classifier, externalDb.getClassifiers().getFirst().getClassifierBeforeMapper()); } @Test @@ -3047,6 +3047,9 @@ private Restore getRestore(String restoreName, String namespace) { SortedMap classifier = new TreeMap<>(); classifier.put("namespace", namespace); + Classifier classifierMapper = new Classifier(); + classifierMapper.setClassifierBeforeMapper(classifier); + List restoreDatabases = new ArrayList<>(); for (int i = 0; i < 3; i++) { RestoreDatabase restoreDatabase = RestoreDatabase.builder() @@ -3055,7 +3058,7 @@ private Restore getRestore(String restoreName, String namespace) { new RestoreDatabase.User("username", "admin") )) .path("path") - .classifiers(List.of(classifier)) + .classifiers(List.of(classifierMapper)) .build(); restoreDatabases.add(restoreDatabase); } @@ -3137,7 +3140,7 @@ private Map> getDatabase(Map db private RestoreDatabase getRestoreDatabase(BackupDatabase backupDatabase, String dbName, - List> classifiers, + List classifiers, Map settings, String bgVersion, RestoreTaskStatus status, From 6b56a7dda2cb5dbdf0b21ea78ddf4a0ae8740545 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 18 Dec 2025 13:16:05 +0500 Subject: [PATCH 07/46] fix: prevent empty filter from generating invalid WHERE clause --- .../pg/jpa/DatabaseRegistryRepository.java | 7 ++-- .../dbaas/service/DbBackupV2Service.java | 34 ++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java index 96f9a504..b054a392 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java @@ -43,8 +43,7 @@ public List findAllDatabasesByFilter(List filters) { StringBuilder q = new StringBuilder( "SELECT cl.* " + "FROM classifier cl " + - "LEFT JOIN database d ON cl.database_id = d.id " + - "WHERE " + "LEFT JOIN database d ON cl.database_id = d.id " ); int index = 0; @@ -80,7 +79,9 @@ public List findAllDatabasesByFilter(List filters) { orBlock.add(block); index++; } - q.append(String.join(" OR ", orBlock)); + if (!params.isEmpty()) { + q.append("WHERE ").append(String.join(" OR ", orBlock)); + } var query = getEntityManager() .createNativeQuery(q.toString(), DatabaseRegistry.class); diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index bfa0a0f3..a40ddfb5 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -441,14 +441,16 @@ protected Map> getAllDbByFilter(FilterCriteria .filter(registry -> { if (!isValidRegistry(registry)) return false; - return filterCriteria.getExclude().stream().noneMatch(exclude -> { - boolean configurational = registry.getBgVersion() != null && !registry.getBgVersion().isBlank(); - return isMatches(exclude, - (String) registry.getClassifier().get(NAMESPACE), - (String) registry.getClassifier().get(MICROSERVICE_NAME), - registry.getType(), - configurational); - }); + return filterCriteria.getExclude().stream() + .filter(exclude -> !isEmpty(exclude)) + .noneMatch(exclude -> { + boolean configurational = registry.getBgVersion() != null && !registry.getBgVersion().isBlank(); + return isMatches(exclude, + (String) registry.getClassifier().get(NAMESPACE), + (String) registry.getClassifier().get(MICROSERVICE_NAME), + registry.getType(), + configurational); + }); }) .toList(); @@ -489,6 +491,13 @@ private boolean isMatches(Filter filter, String namespace, String microserviceNa return true; } + private boolean isEmpty(Filter f) { + return f.getNamespace().isEmpty() + && f.getMicroserviceName().isEmpty() + && f.getDatabaseType().isEmpty() + && f.getDatabaseKind().isEmpty(); + } + private boolean isKindMatched(boolean configurational, DatabaseKind kind) { if (kind == DatabaseKind.CONFIGURATION) return configurational; @@ -721,7 +730,7 @@ protected List getAllDbByFilter(List bac String type = db.getLogicalBackup().getType(); boolean configurational = db.isConfigurational(); return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) - && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); + && filterCriteria.getExclude().stream().filter(exclude -> !isEmpty(exclude)).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) .map(c -> (SortedMap) new TreeMap<>(c)) .toList(); @@ -815,8 +824,8 @@ protected Restore initializeFullRestoreStructure( } protected List validateAndFilterExternalDb(List externalDatabases, - ExternalDatabaseStrategy strategy, - FilterCriteria filterCriteria) { + ExternalDatabaseStrategy strategy, + FilterCriteria filterCriteria) { if (externalDatabases == null || externalDatabases.isEmpty()) return List.of(); @@ -850,7 +859,8 @@ protected List validateAndFilterExternalDb(List isMatches(filter, namespace, microserviceName, type, false)) - && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); + && + filterCriteria.getExclude().stream().filter(exclude -> !isEmpty(exclude)).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); }) .map(c -> (SortedMap) new TreeMap<>(c)) .toList(); From 891c12f2145277510ed7f6c9b2ddbec3b9bc52da Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 19 Dec 2025 16:26:44 +0500 Subject: [PATCH 08/46] fix: prevent state-changing logic during dry-run in findSimilarDbByClassifier --- .../cloud/dbaas/service/DbBackupV2Service.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 580eb5a7..909774b5 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -702,13 +702,13 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, restore.getLogicalRestores().forEach(lr -> { for (RestoreDatabase restoreDatabase : lr.getRestoreDatabases()) { restoreDatabase.setClassifiers( - findSimilarDbByClassifier(restoreDatabase.getClassifiers(), lr.getType()).stream() + findSimilarDbByClassifier(restoreDatabase.getClassifiers(), lr.getType(), true).stream() .toList() ); } }); restore.getExternalDatabases().forEach(externalDb -> { - externalDb.setClassifiers(findSimilarDbByClassifier(externalDb.getClassifiers(), externalDb.getType()).stream().toList()); + externalDb.setClassifiers(findSimilarDbByClassifier(externalDb.getClassifiers(), externalDb.getType(), true).stream().toList()); }); } return mapper.toRestoreResponse(restore); @@ -1310,7 +1310,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { String type = logicalRestore.getType(); log.info("Processing restoreDatabase={}", restoreDatabase.getName()); - Set classifiers = findSimilarDbByClassifier(restoreDatabase.getClassifiers(), type); + Set classifiers = findSimilarDbByClassifier(restoreDatabase.getClassifiers(), type, false); String adapterId = logicalRestore.getAdapterId(); String physicalDatabaseId = physicalDatabasesService.getByAdapterId(adapterId).getPhysicalDatabaseIdentifier(); List ensuredUsers = dbNameToEnsuredUsers.get(restoreDatabase.getName()); @@ -1338,7 +1338,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { log.info("Processing externalDatabase={}, type={}", externalDatabase.getName(), externalDatabase.getType()); String type = externalDatabase.getType(); - Set classifiers = findSimilarDbByClassifier(externalDatabase.getClassifiers(), type); + Set classifiers = findSimilarDbByClassifier(externalDatabase.getClassifiers(), type, false); Database newDatabase = createLogicalDatabase( externalDatabase.getName(), null, @@ -1364,7 +1364,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map findSimilarDbByClassifier(List classifiers, - String type) { + String type, boolean dryRun) { Set uniqueClassifiers = new HashSet<>(); classifiers.forEach(classifier -> { SortedMap currClassifier = classifier.getClassifier() != null ? classifier.getClassifier() @@ -1391,9 +1391,11 @@ private Set findSimilarDbByClassifier(List classifiers, .collect(Collectors.toSet()); uniqueClassifiers.addAll(existClassifiers); - dBaaService.markDatabasesAsOrphan(dbRegistry); - log.info("Database {} marked as orphan", db.getId()); - databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); + if(!dryRun) { + dBaaService.markDatabasesAsOrphan(dbRegistry); + log.info("Database {} marked as orphan", db.getId()); + databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); + } } else uniqueClassifiers.add(classifier); }); From 9ba34878bf83c26692bfe4e75830c641f4dbc15c Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 22 Dec 2025 12:01:44 +0500 Subject: [PATCH 09/46] test: add tests for dry-run --- .../dbaas/service/DbBackupV2ServiceTest.java | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index b5164798..9a0e11a6 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -1581,6 +1581,162 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { } + @Test + void restore_dryRun() { + String restoreName = "restoreName"; + String backupName = "backupName"; + String logicalBackupName = "logicalBackupName"; + String logicalRestoreName = "logicalRestoreName"; + + String dbName = "dbName"; + String newName = "newName"; + String adapterId = "adapterId"; + + String externalDbName = "externalDbName"; + + String namespace = "namespace"; + String mappedNamespace = "mappedNamespace"; + String anotherNamespace = "anotherNamespace"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + + String tenantId = "tenantId"; + String anotherTenantId = "tenantId"; + String mappedTenantId = "mappedTenantId"; + String postgresType = "postgresql"; + + // Database with registries that exists in another namespace + Database database = getDatabase(adapterId, newName, false, false, null); + Database externalDb = getDatabase(null, externalDbName, true, false, null); + DatabaseRegistry registry1 = getDatabaseRegistry(database, mappedNamespace, microserviceName1, mappedTenantId, postgresType); + DatabaseRegistry registry2 = getDatabaseRegistry(database, anotherNamespace, microserviceName1, anotherTenantId, postgresType); + DatabaseRegistry externalRegistry = getDatabaseRegistry(externalDb, mappedNamespace, microserviceName2, mappedTenantId, postgresType); + + databaseRegistryDbaasRepository.saveAnyTypeLogDb(registry1); + databaseRegistryDbaasRepository.saveExternalDatabase(externalRegistry); + + BackupExternalDatabase externalDatabase = getBackupExternalDatabase(externalDbName, postgresType, List.of(getClassifier(namespace, microserviceName2, tenantId))); + BackupDatabase backupDatabase = getBackupDatabase(dbName, List.of(getClassifier(namespace, microserviceName1, tenantId)), false, BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup = getLogicalBackup(logicalBackupName, adapterId, postgresType, List.of(backupDatabase), BackupTaskStatus.COMPLETED, null); + Backup backup = getBackup(backupName, ExternalDatabaseStrategy.INCLUDE, getFilterCriteriaEntity(List.of(namespace)), List.of(logicalBackup), List.of(externalDatabase), BackupStatus.COMPLETED, null); + backupRepository.save(backup); + + DbaasAdapter dbaasAdapter = Mockito.mock(DbaasAdapter.class); + + when(physicalDatabasesService.getAdapterById(adapterId)).thenReturn(dbaasAdapter); + when(dbaasAdapter.isBackupRestoreSupported()).thenReturn(true); + + // Mock logic of choosing adapter in new/current env + ExternalAdapterRegistrationEntry adapter1 = new ExternalAdapterRegistrationEntry(); + adapter1.setAdapterId(adapterId); + PhysicalDatabase physicalDatabase1 = new PhysicalDatabase(); + physicalDatabase1.setAdapter(adapter1); + physicalDatabase1.setType(postgresType); + physicalDatabase1.setPhysicalDatabaseIdentifier("postgres-dev"); + + when(balancingRulesService.applyBalancingRules(postgresType, mappedNamespace, microserviceName1)) + .thenReturn(physicalDatabase1); + when(physicalDatabasesService.getByAdapterId(adapterId)).thenReturn(physicalDatabase1); + + // Response during the sync restore process + LogicalRestoreAdapterResponse response = LogicalRestoreAdapterResponse.builder() + .status(IN_PROGRESS_STATUS) + .restoreId(logicalRestoreName) + .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() + .status(IN_PROGRESS_STATUS) + .previousDatabaseName(dbName) + .databaseName(newName) + .duration(1) + .build())) + .build(); + + // Answer to DryRun + when(dbaasAdapter.restoreV2(eq(logicalBackupName), anyBoolean(), any())) + .thenReturn(response); + + Map namespaceMap = Map.of(namespace, mappedNamespace); + Map tenantMap = Map.of(tenantId, mappedTenantId); + + RestoreResponse restoreResponse = dbBackupV2Service.restore(backupName, + getRestoreRequest(restoreName, List.of(namespace), ExternalDatabaseStrategy.INCLUDE, namespaceMap, tenantMap), + true + ); + assertNull(restoreRepository.findById(restoreName)); + assertNotNull(restoreResponse); + assertEquals(restoreName, restoreResponse.getRestoreName()); + assertEquals(backupName, restoreResponse.getBackupName()); + assertEquals(1, restoreResponse.getLogicalRestores().size()); + + LogicalRestoreResponse logicalRestore = restoreResponse.getLogicalRestores().getFirst(); + assertEquals(logicalRestoreName, logicalRestore.getLogicalRestoreName()); + assertEquals(adapterId, logicalRestore.getAdapterId()); + assertEquals(postgresType, logicalRestore.getType()); + assertEquals(1, logicalRestore.getRestoreDatabases().size()); + + RestoreDatabaseResponse restoreDatabase = logicalRestore.getRestoreDatabases().getFirst(); + assertEquals(newName, restoreDatabase.getName()); + assertEquals(1, restoreDatabase.getUsers().size()); + assertEquals(1, restoreDatabase.getSettings().size()); + assertEquals(2, restoreDatabase.getClassifiers().size()); + + Classifier classifier1 = restoreDatabase.getClassifiers().stream() + .filter(classifier -> ClassifierType.REPLACED == classifier.getType()) + .findAny().orElse(null); + assertNotNull(classifier1); + assertNotNull(classifier1.getClassifier()); + assertTrue( + mappedNamespace.equals(classifier1.getClassifier().get(NAMESPACE)) && + microserviceName1.equals(classifier1.getClassifier().get(MICROSERVICE_NAME)) && + "tenant".equals(classifier1.getClassifier().get("scope")) && + mappedTenantId.equals(classifier1.getClassifier().get(TENANT_ID)) + ); + assertNotNull(classifier1.getClassifierBeforeMapper()); + assertTrue( + namespace.equals(classifier1.getClassifierBeforeMapper().get(NAMESPACE)) && + microserviceName1.equals(classifier1.getClassifierBeforeMapper().get(MICROSERVICE_NAME)) && + "tenant".equals(classifier1.getClassifierBeforeMapper().get("scope")) && + tenantId.equals(classifier1.getClassifierBeforeMapper().get(TENANT_ID)) + ); + + Classifier classifier2 = restoreDatabase.getClassifiers().stream() + .filter(classifier -> ClassifierType.TRANSIENT_REPLACED == classifier.getType()) + .findAny().orElse(null); + assertNotNull(classifier2); + assertNotNull(classifier2.getClassifier()); + assertTrue( + anotherNamespace.equals(classifier2.getClassifier().get(NAMESPACE)) && + microserviceName1.equals(classifier2.getClassifier().get(MICROSERVICE_NAME)) && + "tenant".equals(classifier2.getClassifier().get("scope")) && + tenantId.equals(classifier2.getClassifier().get(TENANT_ID)) + ); + assertNull(classifier2.getClassifierBeforeMapper()); + assertEquals(1, restoreResponse.getExternalDatabases().size()); + + RestoreExternalDatabaseResponse externalDatabaseResponse = restoreResponse.getExternalDatabases().getFirst(); + assertEquals(externalDbName, externalDatabaseResponse.getName()); + assertEquals(postgresType, externalDatabaseResponse.getType()); + assertEquals(1, externalDatabaseResponse.getClassifiers().size()); + + Classifier classifier3 = externalDatabaseResponse.getClassifiers().getFirst(); + assertNotNull(classifier3); + assertEquals(ClassifierType.REPLACED, classifier3.getType()); + assertNotNull(classifier3.getClassifier()); + assertTrue( + mappedNamespace.equals(classifier3.getClassifier().get(NAMESPACE)) && + microserviceName2.equals(classifier3.getClassifier().get(MICROSERVICE_NAME)) && + "tenant".equals(classifier3.getClassifier().get("scope")) && + mappedTenantId.equals(classifier3.getClassifier().get(TENANT_ID)) + ); + assertNotNull(classifier3.getClassifierBeforeMapper()); + assertTrue( + namespace.equals(classifier3.getClassifierBeforeMapper().get(NAMESPACE)) && + microserviceName2.equals(classifier3.getClassifierBeforeMapper().get(MICROSERVICE_NAME)) && + "tenant".equals(classifier3.getClassifierBeforeMapper().get("scope")) && + tenantId.equals(classifier3.getClassifierBeforeMapper().get(TENANT_ID)) + ); + } + @Test void getAllDbByFilter_1() { String namespace1 = "namespace1"; From 7380508da86bf38e99d3d22f0c45dddd280ebb88 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 22 Dec 2025 13:18:06 +0500 Subject: [PATCH 10/46] refactor: reorganize packages --- .../dbaas/converter/ClassifierConverter.java | 10 +++---- .../dto/backupV2/ClassifierResponse.java | 17 +++++++++++ .../dto/backupV2/RestoreDatabaseResponse.java | 3 +- .../RestoreExternalDatabaseResponse.java | 4 +-- .../dto/backupV2/BackupDatabaseDelegate.java | 3 +- .../dto/backupV2/BackupExternalDelegate.java | 2 +- .../pg}/backupV2/Classifier.java | 3 +- .../entity/pg/backupV2/RestoreDatabase.java | 2 -- .../pg/backupV2/RestoreExternalDatabase.java | 2 -- .../backupV2 => enums}/ClassifierType.java | 2 +- .../cloud/dbaas/mapper/BackupV2Mapper.java | 3 ++ .../dbaas/service/DbBackupV2ServiceTest.java | 6 ++-- docs/OpenAPI.json | 30 ++++++++++++++++--- 13 files changed, 63 insertions(+), 24 deletions(-) create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java rename dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/{dto => entity/pg}/backupV2/Classifier.java (87%) rename dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/{dto/backupV2 => enums}/ClassifierType.java (58%) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java index a14e7cfc..a164b095 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/converter/ClassifierConverter.java @@ -3,17 +3,17 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; import jakarta.persistence.AttributeConverter; import java.io.IOException; +import java.util.SortedMap; -public class ClassifierConverter implements AttributeConverter { +public class ClassifierConverter implements AttributeConverter, String> { private ObjectMapper objectMapper = new ObjectMapper(); @Override - public String convertToDatabaseColumn(Classifier attribute) { + public String convertToDatabaseColumn(SortedMap attribute) { try { return objectMapper.writeValueAsString(attribute); } catch (JsonProcessingException e) { @@ -22,9 +22,9 @@ public String convertToDatabaseColumn(Classifier attribute) { } @Override - public Classifier convertToEntityAttribute(String dbData) { + public SortedMap convertToEntityAttribute(String dbData) { try { - return objectMapper.readValue(dbData, new TypeReference<>() { + return objectMapper.readValue(dbData, new TypeReference>() { }); } catch (IOException e) { throw new RuntimeException(e); diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java new file mode 100644 index 00000000..e965aa34 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java @@ -0,0 +1,17 @@ +package com.netcracker.cloud.dbaas.dto.backupV2; + +import com.netcracker.cloud.dbaas.enums.ClassifierType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.SortedMap; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassifierResponse { + private ClassifierType type; + private SortedMap classifier; + private SortedMap classifierBeforeMapper; +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java index 5a7615c7..6799808c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java @@ -1,5 +1,6 @@ package com.netcracker.cloud.dbaas.dto.backupV2; +import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; import com.netcracker.cloud.dbaas.enums.RestoreTaskStatus; import lombok.AllArgsConstructor; import lombok.Data; @@ -26,7 +27,7 @@ public class RestoreDatabaseResponse { description = "List of database classifiers. Each classifier is a sorted map of attributes.", examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]" ) - private List classifiers; + private List classifiers; @Schema( description = "List of database users", examples = "[{\"name\":\"username\",\"role\":\"admin\"}" diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index 2350462d..189e5feb 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -1,11 +1,11 @@ package com.netcracker.cloud.dbaas.dto.backupV2; +import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; import lombok.Data; import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.media.Schema; import java.util.List; -import java.util.SortedMap; @Data @NoArgsConstructor @@ -19,6 +19,6 @@ public class RestoreExternalDatabaseResponse { description = "List of database classifiers. Each classifier is a sorted map of attributes.", examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]" ) - private List classifiers; + private List classifiers; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java index 110c72d9..7bd56f81 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java @@ -1,10 +1,9 @@ package com.netcracker.cloud.dbaas.entity.dto.backupV2; -import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; +import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; import com.netcracker.cloud.dbaas.entity.pg.backupV2.BackupDatabase; import java.util.List; -import java.util.SortedMap; public record BackupDatabaseDelegate(BackupDatabase backupDatabase, diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java index 3d981826..34849b3e 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java @@ -1,6 +1,6 @@ package com.netcracker.cloud.dbaas.entity.dto.backupV2; -import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; +import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; import com.netcracker.cloud.dbaas.entity.pg.backupV2.BackupExternalDatabase; import java.util.List; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java similarity index 87% rename from dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java rename to dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java index f4b8f345..b770bbc6 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/Classifier.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java @@ -1,5 +1,6 @@ -package com.netcracker.cloud.dbaas.dto.backupV2; +package com.netcracker.cloud.dbaas.entity.pg.backupV2; +import com.netcracker.cloud.dbaas.enums.ClassifierType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java index 0cbf4eb9..1a35a75c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java @@ -1,6 +1,5 @@ package com.netcracker.cloud.dbaas.entity.pg.backupV2; -import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; import com.netcracker.cloud.dbaas.enums.RestoreTaskStatus; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; @@ -14,7 +13,6 @@ import java.time.Instant; import java.util.List; import java.util.Map; -import java.util.SortedMap; import java.util.UUID; @Data diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java index eb10429b..d3327d21 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java @@ -1,7 +1,6 @@ package com.netcracker.cloud.dbaas.entity.pg.backupV2; import com.fasterxml.jackson.annotation.JsonBackReference; -import com.netcracker.cloud.dbaas.dto.backupV2.Classifier; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -12,7 +11,6 @@ import org.hibernate.type.SqlTypes; import java.util.List; -import java.util.SortedMap; import java.util.UUID; @Data diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierType.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/ClassifierType.java similarity index 58% rename from dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierType.java rename to dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/ClassifierType.java index c52e73bb..770fc0ea 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierType.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/ClassifierType.java @@ -1,4 +1,4 @@ -package com.netcracker.cloud.dbaas.dto.backupV2; +package com.netcracker.cloud.dbaas.enums; public enum ClassifierType { NEW, REPLACED, TRANSIENT_REPLACED diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java index 481e1a69..08ce7e74 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java @@ -107,4 +107,7 @@ private static , R extends Enum> R mapStatus( RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDelegate backupExternalDelegate); List toRestoreExternalDatabases(List backupExternalDelegates); + + ClassifierResponse toClassifierResponse(Classifier classifier); + List toClassifierResponse(List classifiers); } diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index 9a0e11a6..a61285db 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -1680,7 +1680,7 @@ void restore_dryRun() { assertEquals(1, restoreDatabase.getSettings().size()); assertEquals(2, restoreDatabase.getClassifiers().size()); - Classifier classifier1 = restoreDatabase.getClassifiers().stream() + ClassifierResponse classifier1 = restoreDatabase.getClassifiers().stream() .filter(classifier -> ClassifierType.REPLACED == classifier.getType()) .findAny().orElse(null); assertNotNull(classifier1); @@ -1699,7 +1699,7 @@ void restore_dryRun() { tenantId.equals(classifier1.getClassifierBeforeMapper().get(TENANT_ID)) ); - Classifier classifier2 = restoreDatabase.getClassifiers().stream() + ClassifierResponse classifier2 = restoreDatabase.getClassifiers().stream() .filter(classifier -> ClassifierType.TRANSIENT_REPLACED == classifier.getType()) .findAny().orElse(null); assertNotNull(classifier2); @@ -1718,7 +1718,7 @@ void restore_dryRun() { assertEquals(postgresType, externalDatabaseResponse.getType()); assertEquals(1, externalDatabaseResponse.getClassifiers().size()); - Classifier classifier3 = externalDatabaseResponse.getClassifiers().getFirst(); + ClassifierResponse classifier3 = externalDatabaseResponse.getClassifiers().getFirst(); assertNotNull(classifier3); assertEquals(ClassifierType.REPLACED, classifier3.getType()); assertNotNull(classifier3.getClassifier()); diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index 3775c7bd..751bf505 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -640,6 +640,30 @@ } } }, + "ClassifierResponse": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/ClassifierType" + }, + "classifier": { + "type": "object", + "additionalProperties": {} + }, + "classifierBeforeMapper": { + "type": "object", + "additionalProperties": {} + } + } + }, + "ClassifierType": { + "type": "string", + "enum": [ + "NEW", + "REPLACED", + "TRANSIENT_REPLACED" + ] + }, "ClassifierWithRolesRequest": { "type": "object", "required": [ @@ -3078,8 +3102,7 @@ ] ], "items": { - "type": "object", - "additionalProperties": {} + "$ref": "#/components/schemas/ClassifierResponse" }, "description": "List of database classifiers. Each classifier is a sorted map of attributes." }, @@ -3186,8 +3209,7 @@ ] ], "items": { - "type": "object", - "additionalProperties": {} + "$ref": "#/components/schemas/ClassifierResponse" }, "description": "List of database classifiers. Each classifier is a sorted map of attributes." } From d279bd21a6c21b055f8d347b17b67b5e3a36f9ce Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 22 Dec 2025 16:38:43 +0500 Subject: [PATCH 11/46] refactor: remove attemptCount, duration fields --- .../dto/backupV2/RestoreDatabaseResponse.java | 3 +-- .../RestoreExternalDatabaseResponse.java | 1 - .../dbaas/dto/backupV2/RestoreResponse.java | 4 ---- .../cloud/dbaas/service/DbBackupV2Service.java | 2 +- .../dbaas/service/DbBackupV2ServiceTest.java | 2 -- docs/OpenAPI.json | 16 ---------------- 6 files changed, 2 insertions(+), 26 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java index 6799808c..d3f0669f 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java @@ -1,6 +1,5 @@ package com.netcracker.cloud.dbaas.dto.backupV2; -import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; import com.netcracker.cloud.dbaas.enums.RestoreTaskStatus; import lombok.AllArgsConstructor; import lombok.Data; @@ -59,7 +58,7 @@ public class RestoreDatabaseResponse { required = true ) private String path; - @Schema(description = "Error message if the backup failed", examples = "Restore Not Found") + @Schema(description = "Error message if the backup failed", examples = "Restore Not Found") private String errorMessage; @Schema(description = "Timestamp when the restore was created", examples = "2025-11-13T12:34:56Z") private Instant creationTime; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index 189e5feb..112480be 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -1,6 +1,5 @@ package com.netcracker.cloud.dbaas.dto.backupV2; -import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; import lombok.Data; import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.media.Schema; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java index 333b7e17..9904b20c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java @@ -89,10 +89,6 @@ public class RestoreResponse { examples = "Backup Not Found" ) private String errorMessage; - @Schema(description = "Aggregated duration of databases", examples = "1200") - private Long duration; - @Schema(description = "Total number of adapter requests", examples = "1") - private Integer attemptCount; @Schema( description = "List of logical restores", implementation = LogicalRestoreResponse.class, diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 909774b5..c6e6132a 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -900,9 +900,9 @@ protected List validateAndFilterExternalDb(List executeMappingForExternalDb(List externalDatabases, Mapping mapping) { + Set> uniqueClassifiers = new HashSet<>(); return externalDatabases.stream() .peek(db -> { - Set> uniqueClassifiers = new HashSet<>(); List updatedClassifiers = db.getClassifiers().stream() .map(classifier -> updateAndValidateClassifier(classifier, mapping, uniqueClassifiers)) .toList(); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index a61285db..a816b869 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -892,7 +892,6 @@ void restore_withoutMapping_finishedWithStatusCompleted() { assertNotNull(restoreResponse); assertEquals(restoreName, restoreResponse.getRestoreName()); assertEquals(RestoreStatus.IN_PROGRESS, restoreResponse.getStatus()); - assertEquals(2, restoreResponse.getDuration()); dbBackupV2Service.checkRestoresAsync(); Restore restore = restoreRepository.findById(restoreName); @@ -1173,7 +1172,6 @@ void restore_withMapping_finishedWithStatusCompleted() { assertNotNull(restoreResponse); assertEquals(restoreName, restoreResponse.getRestoreName()); assertEquals(RestoreStatus.IN_PROGRESS, restoreResponse.getStatus()); - assertEquals(2, restoreResponse.getDuration()); dbBackupV2Service.checkRestoresAsync(); Restore restore = restoreRepository.findById(restoreName); diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index 751bf505..91e6d59b 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -3482,22 +3482,6 @@ ], "description": "Aggregated error messages during restore operation" }, - "duration": { - "type": "integer", - "format": "int64", - "examples": [ - 1200 - ], - "description": "Aggregated duration of databases" - }, - "attemptCount": { - "type": "integer", - "format": "int32", - "examples": [ - 1 - ], - "description": "Total number of adapter requests" - }, "logicalRestores": { "type": "array", "items": { From 398e38590d195dbdc66ba597e5590104e97dc954 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 25 Dec 2025 14:30:04 +0500 Subject: [PATCH 12/46] feat: implement retry restore --- .../v3/DatabaseBackupV2Controller.java | 5 +- .../dbaas/service/DbBackupV2Service.java | 140 ++++++++--- .../dbaas/service/DbBackupV2ServiceTest.java | 233 +++++++++++++++++- docs/OpenAPI.json | 10 - 4 files changed, 342 insertions(+), 46 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index 0d22139f..5b98ea0c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -308,8 +308,6 @@ public Response getRestoreStatus(@Parameter(description = "Unique identifier of @Operation(summary = "Retry restore", description = "Retry a failed restore operation") @APIResponses({ - @APIResponse(responseCode = "200", description = "Restore operation retried successfully", - content = @Content(schema = @Schema(implementation = RestoreResponse.class))), @APIResponse(responseCode = "202", description = "Restore retry accepted and is being processed", content = @Content(schema = @Schema(implementation = RestoreResponse.class))), @APIResponse(responseCode = "401", description = "Authentication is required and has failed or has not been provided"), @@ -324,7 +322,6 @@ public Response getRestoreStatus(@Parameter(description = "Unique identifier of public Response retryRestore(@Parameter(description = "Unique identifier of the restore operation", required = true) @PathParam("restoreName") String restoreName) { - dbBackupV2Service.retryRestore(restoreName); - return Response.ok().build(); + return Response.accepted(dbBackupV2Service.retryRestore(restoreName)).build(); } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index c6e6132a..6ce1c4d2 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -1035,26 +1035,33 @@ private Classifier updateAndValidateClassifier( protected void startRestore(Restore restore, boolean dryRun) { List logicalRestores = restore.getLogicalRestores(); log.info("Starting requesting adapters to restore startup process: restore={}, dryRun={} logicalRestoreCount={}", - restore.getName(), dryRun, restore.getLogicalRestores().size()); + restore.getName(), dryRun, logicalRestores.size()); + String storageName = restore.getStorageName(); + String blobPath = restore.getBlobPath(); + List> futures = logicalRestores.stream() .map(logicalRestore -> - CompletableFuture.supplyAsync(asyncOperations.wrapWithContext(() -> - logicalRestore(logicalRestore, dryRun))) - .thenAccept(response -> - refreshLogicalRestoreState(logicalRestore, response)) - .exceptionally(throwable -> { - logicalRestore.setStatus(RestoreTaskStatus.FAILED); - logicalRestore.setErrorMessage(extractErrorMessage(throwable)); - log.error("Logical restore failed: adapterId={}, error={}", - logicalRestore.getAdapterId(), logicalRestore.getErrorMessage()); - return null; - }) + runLogicalRestoreAsync(logicalRestore, logicalRestore.getRestoreDatabases(), storageName, blobPath, dryRun) ) .toList(); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); } + private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRestore, List restoreDatabases, String storageName, String blobPath, boolean dryRun) { + return CompletableFuture.supplyAsync(asyncOperations.wrapWithContext(() -> + logicalRestore(logicalRestore.getLogicalRestoreName(), logicalRestore, restoreDatabases, storageName, blobPath, dryRun))) + .thenAccept(response -> + refreshLogicalRestoreState(logicalRestore, response)) + .exceptionally(throwable -> { + logicalRestore.setStatus(RestoreTaskStatus.FAILED); + logicalRestore.setErrorMessage(extractErrorMessage(throwable)); + log.error("Logical restore failed: adapterId={}, error={}", + logicalRestore.getAdapterId(), logicalRestore.getErrorMessage()); + return null; + }); + } + private void refreshLogicalRestoreState(LogicalRestore logicalRestore, LogicalRestoreAdapterResponse response) { log.info("Starting LogicalRestore state update [restoreName={}, logicalRestoreName={}]", logicalRestore.getRestore().getName(), @@ -1099,22 +1106,21 @@ private void refreshLogicalRestoreState(LogicalRestore logicalRestore, LogicalRe logicalRestore.getLogicalRestoreName(), logicalRestore.getStatus(), logicalRestore.getErrorMessage()); } - private LogicalRestoreAdapterResponse logicalRestore(LogicalRestore logicalRestore, boolean dryRun) { - String logicalBackupName = logicalRestore.getRestoreDatabases().getFirst() + private LogicalRestoreAdapterResponse logicalRestore(String logicalRestoreName, LogicalRestore logicalRestore, List restoreDatabases, String storageName, String blobPath, boolean dryRun) { + String logicalBackupName = restoreDatabases.getFirst() .getBackupDatabase() .getLogicalBackup() .getLogicalBackupName(); - List> databases = buildRestoreDatabases(logicalRestore); - Restore restore = logicalRestore.getRestore(); - RetryPolicy retryPolicy = buildRetryPolicy(logicalRestore.getLogicalRestoreName(), RESTORE_OPERATION); + List> databases = buildRestoreDatabases(restoreDatabases); + RetryPolicy retryPolicy = buildRetryPolicy(logicalRestoreName, RESTORE_OPERATION); return Failsafe.with(retryPolicy) - .get(() -> executeRestore(logicalRestore, logicalBackupName, restore, databases, dryRun)); + .get(() -> executeRestore(logicalRestore, logicalBackupName, storageName, blobPath, databases, dryRun)); } - private List> buildRestoreDatabases(LogicalRestore logicalRestore) { - return logicalRestore.getRestoreDatabases().stream() + private List> buildRestoreDatabases(List restoreDatabases) { + return restoreDatabases.stream() .map(restoreDatabase -> { String namespace = restoreDatabase.getClassifiers().stream() .map(c -> c.getClassifier() != null ? (String) c.getClassifier().get(NAMESPACE) : (String) c.getClassifierBeforeMapper().get(NAMESPACE)) @@ -1128,7 +1134,7 @@ private List> buildRestoreDatabases(LogicalRestore logicalRe return Map.of( MICROSERVICE_NAME, microserviceName, - DATABASE_NAME, restoreDatabase.getName(), + DATABASE_NAME, restoreDatabase.getBackupDatabase().getName(), NAMESPACE, namespace ); }) @@ -1138,7 +1144,8 @@ private List> buildRestoreDatabases(LogicalRestore logicalRe private LogicalRestoreAdapterResponse executeRestore( LogicalRestore logicalRestore, String logicalBackupName, - Restore restore, + String storageName, + String blobPath, List> databases, boolean dryRun ) { @@ -1147,7 +1154,7 @@ private LogicalRestoreAdapterResponse executeRestore( LogicalRestoreAdapterResponse result = adapter.restoreV2( logicalBackupName, dryRun, - new RestoreAdapterRequest(restore.getStorageName(), restore.getBlobPath(), databases) + new RestoreAdapterRequest(storageName, blobPath, databases) ); if (result == null) { @@ -1198,14 +1205,19 @@ protected List ensureUsers(String adapterId, DbaasAdapter adapter = physicalDatabasesService.getAdapterById(adapterId); RetryPolicy retryPolicy = buildRetryPolicy(dbName, ENSURE_USER_OPERATION); - log.info("Ensuring {} users for databaseName=[{}] via adapter [{}]", + log.info("Start ensure {} users for database=[{}] via adapter [{}]", users.size(), dbName, adapterId); return users.stream() .map(user -> { try { return Failsafe.with(retryPolicy) - .get(() -> adapter.ensureUser(user.getName(), null, dbName, user.getRole())); + .get(() -> { + EnsuredUser ensuredUser = adapter.ensureUser(user.getName(), null, dbName, user.getRole()); + log.info("User ensured for database=[{}], user information=[name={}, connectionProperties={}]", + dbName, ensuredUser.getName(), ensuredUser.getConnectionProperties()); + return ensuredUser; + }); } catch (Exception e) { log.error("Failed to ensure user {} in database {}", user.getName(), dbName, e); throw new BackupExecutionException( @@ -1331,7 +1343,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map findSimilarDbByClassifier(List classifiers, .collect(Collectors.toSet()); uniqueClassifiers.addAll(existClassifiers); - if(!dryRun) { + if (!dryRun) { dBaaService.markDatabasesAsOrphan(dbRegistry); log.info("Database {} marked as orphan", db.getId()); databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); @@ -1482,7 +1494,77 @@ public void deleteRestore(String restoreName) { } public RestoreResponse retryRestore(String restoreName) { - throw new FunctionalityNotImplemented("retry restore functionality not implemented yet"); + // Check existence of restore by restoreName + Restore restore = getRestoreOrThrowException(restoreName); + // Check if the status of the restor has FAILED + if (RestoreStatus.FAILED != restore.getStatus()) { + throw new UnprocessableEntityException( + restoreName, + String.format( + "has invalid status '%s'. Only %s restores can be processed.", + restore.getStatus(), RestoreStatus.FAILED + ), + Source.builder().build()); + } + // Check if the status of backup has COMPLETED + BackupStatus backupStatus = restore.getBackup().getStatus(); + if (BackupStatus.COMPLETED != backupStatus) { + log.error("Restore can`t process due to backup status {}", backupStatus); + throw new UnprocessableEntityException( + restore.getBackup().getName(), String.format("restore can`t process due to backup status %s", backupStatus), + Source.builder().build()); + } + // Only one retry restore operation able to process + LockConfiguration config = new LockConfiguration( + Instant.now(), + RESTORE, + Duration.ofMinutes(2), + Duration.ofMinutes(0)); + + Optional optLock = lockProvider.lock(config); + + if (optLock.isEmpty()) + throw new IllegalResourceStateException("restore already running", Source.builder().build()); + + SimpleLock lock = optLock.get(); + boolean unlocked = false; + + try { + if (restoreRepository.countNotCompletedRestores() > 0) + throw new IllegalResourceStateException("another restore is being processed", Source.builder().build()); + + retryRestore(restore); + aggregateRestoreStatus(restore); + restoreRepository.save(restore); + return mapper.toRestoreResponse(restore); + } finally { + if (!unlocked) + lock.unlock(); + } + } + + private void retryRestore(Restore restore) { + List failedLogicalRestores = restore.getLogicalRestores() + .stream() + .filter(logicalRestore -> RestoreTaskStatus.FAILED == logicalRestore.getStatus()) + .toList(); + + log.info("Starting retry restore process: restore={}, failedLogicalRestoreCount={}", + restore.getName(), failedLogicalRestores.size()); + String storageName = restore.getStorageName(); + String blobPath = restore.getBlobPath(); + + List> futures = failedLogicalRestores.stream() + .map(logicalRestore -> { + List failedRestoreDatabase = logicalRestore.getRestoreDatabases().stream() + .filter(db -> RestoreTaskStatus.FAILED == db.getStatus()) + .toList(); + return runLogicalRestoreAsync(logicalRestore, failedRestoreDatabase, storageName, blobPath, false); + } + ) + .toList(); + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); } protected Map> validateAndFilterDatabasesForBackup(Map> databasesForBackup, diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index a816b869..e4ceeb50 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -1685,9 +1685,9 @@ void restore_dryRun() { assertNotNull(classifier1.getClassifier()); assertTrue( mappedNamespace.equals(classifier1.getClassifier().get(NAMESPACE)) && - microserviceName1.equals(classifier1.getClassifier().get(MICROSERVICE_NAME)) && - "tenant".equals(classifier1.getClassifier().get("scope")) && - mappedTenantId.equals(classifier1.getClassifier().get(TENANT_ID)) + microserviceName1.equals(classifier1.getClassifier().get(MICROSERVICE_NAME)) && + "tenant".equals(classifier1.getClassifier().get("scope")) && + mappedTenantId.equals(classifier1.getClassifier().get(TENANT_ID)) ); assertNotNull(classifier1.getClassifierBeforeMapper()); assertTrue( @@ -1735,6 +1735,190 @@ void restore_dryRun() { ); } + @Test + void retryRestore() { + // 1 ExternalDatabase 2 RestoreDatabase (1 FAILED, 1 COMPLETED), 1 logicalRestore (FAILED), 1 restore (FAILED) + // 1 mapping { namespace : mappedNamespace } + // Assert restore COMPLETED, 3 db created + String restoreName = "restoreName"; + String backupName = "backupName"; + String logicalBackupName = "logicalBackupName"; + String logicalRestoreName = "logicalRestoreName"; + + String dbName = "dbName"; + String dbName2 = "dbName2"; + + String newName = "newName"; + String newName2 = "newName2"; + String externalName = "externalName"; + String adapterId = "adapterId"; + + + String namespace = "namespace"; + String mappedNamespace = "mappedNamespace"; + String anotherNamespace = "anotherNamespace"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + String microserviceName3 = "microserviceName3"; + + String tenantId = "tenantId"; + String postgresType = "postgresql"; + + BackupExternalDatabase externalDatabase = getBackupExternalDatabase(externalName, postgresType, List.of(getClassifier(namespace, microserviceName3, tenantId))); + BackupDatabase backupDatabase1 = getBackupDatabase(dbName, List.of(getClassifier(namespace, microserviceName1, tenantId)), false, BackupTaskStatus.COMPLETED, "Internal Server Error"); + BackupDatabase backupDatabase2 = getBackupDatabase(dbName2, List.of(getClassifier(anotherNamespace, microserviceName2, null)), false, BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup = getLogicalBackup(logicalBackupName, adapterId, postgresType, List.of(backupDatabase1, backupDatabase2), BackupTaskStatus.COMPLETED, "Internal Server Error"); + Backup backup = getBackup(backupName, ExternalDatabaseStrategy.INCLUDE, getFilterCriteriaEntity(List.of(namespace)), List.of(logicalBackup), List.of(externalDatabase), BackupStatus.COMPLETED, "Internal Server Error"); + backupRepository.save(backup); + + Classifier classifierWrapper1 = getClassifier(ClassifierType.NEW, mappedNamespace, microserviceName1, null, namespace, null); + Classifier classifierWrapper2 = getClassifier(ClassifierType.NEW, null, microserviceName2, null, namespace, null); + Classifier classifierWrapper3 = getClassifier(ClassifierType.NEW, null, microserviceName3, null, namespace, null); + RestoreExternalDatabase restoreExternalDatabase = getRestoreExternalDb(externalName, postgresType, List.of(classifierWrapper3)); + RestoreDatabase restoreDatabase1 = getRestoreDatabase(backupDatabase1, newName, List.of(classifierWrapper1), Map.of(), null, RestoreTaskStatus.FAILED, 1, "Internal Server Error"); + RestoreDatabase restoreDatabase2 = getRestoreDatabase(backupDatabase2, newName2, List.of(classifierWrapper2), Map.of(), null, RestoreTaskStatus.COMPLETED, 1, null); + LogicalRestore logicalRestore = getLogicalRestore(logicalRestoreName, adapterId, postgresType, List.of(restoreDatabase1, restoreDatabase2), RestoreTaskStatus.FAILED, "Internal Server Error"); + Restore restore = getRestore(backup, restoreName, getFilterCriteriaEntity(List.of(namespace)), null, List.of(logicalRestore), ExternalDatabaseStrategy.INCLUDE, List.of(restoreExternalDatabase), RestoreStatus.FAILED, "Internal Server Error"); + + restoreRepository.save(restore); + + DbaasAdapter dbaasAdapter = Mockito.mock(DbaasAdapter.class); + + when(physicalDatabasesService.getAdapterById(adapterId)).thenReturn(dbaasAdapter); + when(dbaasAdapter.isBackupRestoreSupported()).thenReturn(true); + + // Mock logic of choosing adapter in new/current env + ExternalAdapterRegistrationEntry adapter1 = new ExternalAdapterRegistrationEntry(); + adapter1.setAdapterId(adapterId); + PhysicalDatabase physicalDatabase1 = new PhysicalDatabase(); + physicalDatabase1.setAdapter(adapter1); + physicalDatabase1.setType(postgresType); + physicalDatabase1.setPhysicalDatabaseIdentifier("postgres-dev"); + + when(balancingRulesService.applyBalancingRules(postgresType, mappedNamespace, microserviceName1)) + .thenReturn(physicalDatabase1); + when(physicalDatabasesService.getByAdapterId(adapterId)).thenReturn(physicalDatabase1); + + // Response during the sync restore process + LogicalRestoreAdapterResponse response = LogicalRestoreAdapterResponse.builder() + .status(IN_PROGRESS_STATUS) + .restoreId(logicalRestoreName) + .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() + .status(IN_PROGRESS_STATUS) + .previousDatabaseName(dbName) + .databaseName(newName) + .duration(1) + .build())) + .build(); + + // Same answer to DryRun non-DryRun mode + when(dbaasAdapter.restoreV2(eq(logicalBackupName), anyBoolean(), any())) + .thenReturn(response) + .thenReturn(response); + + // Response during the async restore process + LogicalRestoreAdapterResponse response2 = LogicalRestoreAdapterResponse.builder() + .status(COMPLETED_STATUS) + .restoreId(logicalRestoreName) + .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() + .status(COMPLETED_STATUS) + .previousDatabaseName(dbName) + .databaseName(newName) + .duration(1) + .build())) + .build(); + + when(dbaasAdapter.trackRestoreV2(eq(logicalRestoreName), any(), any())) + .thenReturn(response2); + + // Mocks to ensure user process + DbResource resource1 = new DbResource(); + resource1.setId(UUID.randomUUID()); + resource1.setKind("kind"); + resource1.setName("name"); + EnsuredUser user1 = new EnsuredUser(); + user1.setConnectionProperties(Map.of( + "key", "value" + )); + user1.setResources(List.of(resource1)); + + when(dbaasAdapter.ensureUser("username", null, newName, "admin")).thenReturn(user1); + + DbResource resource2 = new DbResource(); + resource2.setId(UUID.randomUUID()); + resource2.setKind("kind"); + resource2.setName("name"); + EnsuredUser user2 = new EnsuredUser(); + user2.setConnectionProperties(Map.of( + "key", "value" + )); + user2.setResources(List.of(resource2)); + + when(dbaasAdapter.ensureUser("username", null, newName2, "admin")).thenReturn(user2); + + RestoreResponse restoreResponse = dbBackupV2Service.retryRestore(restoreName); + dbBackupV2Service.checkRestoresAsync(); + + assertEquals(restoreName, restoreResponse.getRestoreName()); + assertEquals(backupName, restoreResponse.getBackupName()); + assertEquals(RestoreStatus.IN_PROGRESS, restoreResponse.getStatus()); + assertEquals(2, restoreResponse.getTotal()); + assertEquals(1, restoreResponse.getCompleted()); + assertEquals(1, restoreResponse.getLogicalRestores().size()); + + LogicalRestoreResponse logicalRestoreResponse = restoreResponse.getLogicalRestores().getFirst(); + assertEquals(logicalRestoreName, logicalRestoreResponse.getLogicalRestoreName()); + assertEquals(adapterId, logicalRestoreResponse.getAdapterId()); + assertEquals(2, logicalRestoreResponse.getRestoreDatabases().size()); + + RestoreDatabaseResponse restoreDatabaseResponse1 = logicalRestoreResponse.getRestoreDatabases().stream() + .filter(db -> newName.equals(db.getName())).findAny().orElse(null); + assertEquals(1, restoreDatabaseResponse1.getClassifiers().size()); + + ClassifierResponse classifierResponse1 = restoreDatabaseResponse1.getClassifiers().getFirst(); + assertEquals(classifierWrapper1.getType(), classifierResponse1.getType()); + assertEquals(classifierWrapper1.getClassifier(), classifierResponse1.getClassifier()); + assertEquals(classifierWrapper1.getClassifierBeforeMapper(), classifierResponse1.getClassifierBeforeMapper()); + assertEquals(RestoreTaskStatus.IN_PROGRESS, restoreDatabaseResponse1.getStatus()); + + RestoreDatabaseResponse restoreDatabaseResponse2 = logicalRestoreResponse.getRestoreDatabases().stream() + .filter(db -> newName2.equals(db.getName())).findAny().orElse(null); + assertEquals(1, restoreDatabaseResponse2.getClassifiers().size()); + + ClassifierResponse classifierResponse2 = restoreDatabaseResponse2.getClassifiers().getFirst(); + assertEquals(classifierWrapper2.getType(), classifierResponse2.getType()); + assertEquals(classifierWrapper2.getClassifier(), classifierResponse2.getClassifier()); + assertEquals(classifierWrapper2.getClassifierBeforeMapper(), classifierResponse2.getClassifierBeforeMapper()); + assertEquals(RestoreTaskStatus.COMPLETED, restoreDatabaseResponse2.getStatus()); + + // Start assert initialized dbs + List databaseRegistries = databaseRegistryDbaasRepository.findAllDatabaseRegistersAnyLogType(); + assertEquals(3, databaseRegistries.size()); + + DatabaseRegistry databaseRegistry1 = databaseRegistries.stream() + .filter(db -> newName.equals(db.getName())) + .findAny().orElse(null); + assert databaseRegistry1 != null; + assertEquals(classifierWrapper1.getClassifier(), databaseRegistry1.getClassifier()); + assertEquals("postgres-dev", databaseRegistry1.getPhysicalDatabaseId()); + + DatabaseRegistry databaseRegistry2 = databaseRegistries.stream() + .filter(db -> newName2.equals(db.getName())) + .findAny().orElse(null); + assert databaseRegistry2 != null; + assertEquals(classifierWrapper2.getClassifierBeforeMapper(), databaseRegistry2.getClassifier()); + assertEquals("postgres-dev", databaseRegistry2.getPhysicalDatabaseId()); + + DatabaseRegistry databaseRegistry3 = databaseRegistries.stream() + .filter(db -> externalName.equals(db.getName())) + .findAny().orElse(null); + assert databaseRegistry3 != null; + assertEquals(classifierWrapper3.getClassifierBeforeMapper(), databaseRegistry3.getClassifier()); + assertNull(databaseRegistry3.getPhysicalDatabaseId()); + + } + @Test void getAllDbByFilter_1() { String namespace1 = "namespace1"; @@ -3056,6 +3240,37 @@ private SortedMap getClassifier(String namespace, String microse return classifier; } + private Classifier getClassifier(ClassifierType classifierType, String namespace, String microserviceName, String tenantId, String namespaceBeforeMap, String tenantIdBeforeMap) { + assertFalse(namespaceBeforeMap.isBlank()); + + Classifier classifierWrapper = new Classifier(); + classifierWrapper.setType(classifierType); + + if(namespace != null && !namespace.isBlank()) { + SortedMap classifier = new TreeMap<>(); + classifier.put("namespace", namespace); + classifier.put("microserviceName", microserviceName); + if (tenantId != null && !tenantId.isBlank()) { + classifier.put("tenantId", tenantId); + classifier.put("scope", "tenant"); + } else + classifier.put("scope", "service"); + classifierWrapper.setClassifier(classifier); + } + + SortedMap classifierBeforeMapping = new TreeMap<>(); + classifierBeforeMapping.put("namespace", namespaceBeforeMap); + classifierBeforeMapping.put("microserviceName", microserviceName); + if (tenantId != null && !tenantId.isBlank()) { + classifierBeforeMapping.put("tenantId", tenantIdBeforeMap); + classifierBeforeMapping.put("scope", "tenant"); + } else + classifierBeforeMapping.put("scope", "service"); + + classifierWrapper.setClassifierBeforeMapper(classifierBeforeMapping); + return classifierWrapper; + } + private BackupExternalDatabase getBackupExternalDatabase(String name, String type, List> classifiers) { return BackupExternalDatabase.builder() .name(name) @@ -3360,6 +3575,18 @@ private Restore getRestore(Backup backup, return restore; } + private RestoreExternalDatabase getRestoreExternalDb( + String name, + String type, + List classifiers + ) { + return RestoreExternalDatabase.builder() + .name(name) + .type(type) + .classifiers(classifiers) + .build(); + } + private BackupRequest getBackupRequest(String backupName, List namespaces, ExternalDatabaseStrategy strategy, diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index 91e6d59b..7c2e326c 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -4960,16 +4960,6 @@ } ], "responses": { - "200": { - "description": "Restore operation retried successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RestoreResponse" - } - } - } - }, "202": { "description": "Restore retry accepted and is being processed", "content": { From ed614089b0a91db3673a15672124d99078b3c234 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 25 Dec 2025 14:51:54 +0500 Subject: [PATCH 13/46] refactor: conditional return to stream map --- .../dbaas/service/DbBackupV2Service.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index c6e6132a..d4d0b7db 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -950,7 +950,8 @@ private Classifier updateClassifier(Classifier classifier, Mapping mapping) { updatedClassifier.put(NAMESPACE, targetNamespace); if (targetTenant != null) updatedClassifier.put(TENANT_ID, targetTenant); - classifier.setClassifier(updatedClassifier); + if(!targetNamespace.equals(classifier.getClassifierBeforeMapper().get(NAMESPACE))) + classifier.setClassifier(updatedClassifier); return classifier; } @@ -1342,7 +1343,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), type, true, true, @@ -1382,12 +1383,10 @@ private Set findSimilarDbByClassifier(List classifiers, Set existClassifiers = db.getDatabaseRegistry().stream() .map(AbstractDatabaseRegistry::getClassifier) .map(TreeMap::new) - .map(c -> { - if (currClassifier.equals(c)) - return classifier; - else - return wrapClassifier(ClassifierType.TRANSIENT_REPLACED, c, null); - }) + .map(c -> currClassifier.equals(c) + ? classifier + : new Classifier(ClassifierType.TRANSIENT_REPLACED, c, null) + ) .collect(Collectors.toSet()); uniqueClassifiers.addAll(existClassifiers); @@ -1402,8 +1401,8 @@ private Set findSimilarDbByClassifier(List classifiers, return uniqueClassifiers; } - private Classifier wrapClassifier(ClassifierType type, SortedMap classifier, SortedMap classifierBeforeMapper) { - return new Classifier(type, classifier, classifierBeforeMapper); + private Classifier wrapClassifier(SortedMap classifier) { + return new Classifier(ClassifierType.TRANSIENT_REPLACED, classifier, null); } private Database createLogicalDatabase(String dbName, From ea34805d653037c438ef16c996b689bde36ed00a Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 26 Dec 2025 11:23:05 +0500 Subject: [PATCH 14/46] refactor: prevent lock method duplication --- .../dbaas/service/DbBackupV2Service.java | 165 ++++++++---------- 1 file changed, 72 insertions(+), 93 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index f6b298d6..494dc401 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -38,6 +38,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import static com.netcracker.cloud.dbaas.Constants.*; @@ -652,71 +653,44 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, log.info("Start restore for backup {}", backupName); Backup backup = getBackupOrThrowException(backupName); + assertBackupStatusCompleted(backupName, backup.getStatus()); - BackupStatus backupStatus = backup.getStatus(); - if (BackupStatus.COMPLETED != backupStatus) { - log.error("Restore can`t process due to backup status {}", backupStatus); - throw new UnprocessableEntityException( - backupName, String.format("restore can`t process due to backup status %s", backupStatus), - Source.builder().build()); - } - - LockConfiguration config = new LockConfiguration( - Instant.now(), - RESTORE, - Duration.ofMinutes(2), - Duration.ofMinutes(0) - ); - - Optional optLock = lockProvider.lock(config); - - if (optLock.isEmpty()) - throw new IllegalResourceStateException("restore already running", Source.builder().build()); - - SimpleLock lock = optLock.get(); - boolean unlocked = false; - - try { - if (restoreRepository.countNotCompletedRestores() > 0) - throw new IllegalResourceStateException("another restore is being processed", Source.builder().build()); - - Restore restore = initializeFullRestoreStructure(backup, restoreRequest); + Restore restore = restoreLockWrapper(() -> { + Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); if (!dryRun) - restoreRepository.save(restore); - // unlock method after save restore - lock.unlock(); - unlocked = true; + restoreRepository.save(currRestore); + return currRestore; + }); - // DryRun on adapters - startRestore(restore, true); + // DryRun on adapters + startRestore(restore, true); + aggregateRestoreStatus(restore); + if (!dryRun && RestoreStatus.FAILED != restore.getStatus()) { + // Real run on adapters + restore = getRestoreOrThrowException(restoreName); + startRestore(restore, false); aggregateRestoreStatus(restore); - if (!dryRun && RestoreStatus.FAILED != restore.getStatus()) { - // Real run on adapters - restore = getRestoreOrThrowException(restoreName); - startRestore(restore, false); - aggregateRestoreStatus(restore); - } - if (!dryRun) - restoreRepository.save(restore); - else { - restore.getLogicalRestores().forEach(lr -> { - for (RestoreDatabase restoreDatabase : lr.getRestoreDatabases()) { - restoreDatabase.setClassifiers( - findSimilarDbByClassifier(restoreDatabase.getClassifiers(), lr.getType(), true).stream() - .toList() - ); - } - }); - restore.getExternalDatabases().forEach(externalDb -> { - externalDb.setClassifiers(findSimilarDbByClassifier(externalDb.getClassifiers(), externalDb.getType(), true).stream().toList()); - }); - } - return mapper.toRestoreResponse(restore); - } finally { - if (!unlocked) { - lock.unlock(); - } } + if (!dryRun) + restoreRepository.save(restore); + else { + updateRestoreClassifiersOnDryRun(restore.getLogicalRestores(), restore.getExternalDatabases()); + } + return mapper.toRestoreResponse(restore); + } + + private void updateRestoreClassifiersOnDryRun(List logicalRestore, List externalDatabases) { + logicalRestore.forEach(lr -> { + for (RestoreDatabase restoreDatabase : lr.getRestoreDatabases()) { + restoreDatabase.setClassifiers( + findSimilarDbByClassifier(restoreDatabase.getClassifiers(), lr.getType(), true).stream() + .toList() + ); + } + }); + externalDatabases.forEach(externalDb -> + externalDb.setClassifiers(findSimilarDbByClassifier(externalDb.getClassifiers(), externalDb.getType(), true).stream().toList()) + ); } protected List getAllDbByFilter(List backupDatabasesToFilter, FilterCriteria filterCriteria) { @@ -950,7 +924,7 @@ private Classifier updateClassifier(Classifier classifier, Mapping mapping) { updatedClassifier.put(NAMESPACE, targetNamespace); if (targetTenant != null) updatedClassifier.put(TENANT_ID, targetTenant); - if(!targetNamespace.equals(classifier.getClassifierBeforeMapper().get(NAMESPACE))) + if (!targetNamespace.equals(classifier.getClassifierBeforeMapper().get(NAMESPACE))) classifier.setClassifier(updatedClassifier); return classifier; } @@ -1413,10 +1387,6 @@ private Set findSimilarDbByClassifier(List classifiers, return uniqueClassifiers; } - private Classifier wrapClassifier(SortedMap classifier) { - return new Classifier(ClassifierType.TRANSIENT_REPLACED, classifier, null); - } - private Database createLogicalDatabase(String dbName, Map settings, Set> classifiers, @@ -1505,40 +1475,24 @@ public RestoreResponse retryRestore(String restoreName) { ), Source.builder().build()); } - // Check if the status of backup has COMPLETED - BackupStatus backupStatus = restore.getBackup().getStatus(); - if (BackupStatus.COMPLETED != backupStatus) { - log.error("Restore can`t process due to backup status {}", backupStatus); - throw new UnprocessableEntityException( - restore.getBackup().getName(), String.format("restore can`t process due to backup status %s", backupStatus), - Source.builder().build()); - } - // Only one retry restore operation able to process - LockConfiguration config = new LockConfiguration( - Instant.now(), - RESTORE, - Duration.ofMinutes(2), - Duration.ofMinutes(0)); - - Optional optLock = lockProvider.lock(config); - - if (optLock.isEmpty()) - throw new IllegalResourceStateException("restore already running", Source.builder().build()); - - SimpleLock lock = optLock.get(); - boolean unlocked = false; - try { - if (restoreRepository.countNotCompletedRestores() > 0) - throw new IllegalResourceStateException("another restore is being processed", Source.builder().build()); + assertBackupStatusCompleted(restore.getBackup().getName(), restore.getBackup().getStatus()); + return restoreLockWrapper(() -> { retryRestore(restore); aggregateRestoreStatus(restore); restoreRepository.save(restore); return mapper.toRestoreResponse(restore); - } finally { - if (!unlocked) - lock.unlock(); + }); + } + + private void assertBackupStatusCompleted(String backupName, BackupStatus status) { + // Check if the status of backup has COMPLETED + if (BackupStatus.COMPLETED != status) { + log.error("Restore can`t process due to backup status {}", status); + throw new UnprocessableEntityException( + backupName, String.format("restore can`t process due to backup status %s", status), + Source.builder().build()); } } @@ -1661,6 +1615,31 @@ private Restore getRestoreOrThrowException(String restoreName) { .orElseThrow(() -> new BackupRestorationNotFoundException(restoreName, Source.builder().build())); } + private T restoreLockWrapper(Supplier action) { + // Only one retry restore operation able to process + LockConfiguration config = new LockConfiguration( + Instant.now(), + RESTORE, + Duration.ofMinutes(2), + Duration.ofMinutes(0)); + + Optional optLock = lockProvider.lock(config); + + if (optLock.isEmpty()) + throw new IllegalResourceStateException("restore already running", Source.builder().build()); + // Start locking action + SimpleLock lock = optLock.get(); + boolean unlocked = false; + try { + if (restoreRepository.countNotCompletedRestores() > 0) + throw new IllegalResourceStateException("another restore is being processed", Source.builder().build()); + return action.get(); + } finally { + if (!unlocked) + lock.unlock(); + } + } + private RetryPolicy buildRetryPolicy(String name, String operation) { return new RetryPolicy<>() .handle(WebApplicationException.class) From e912a3f038682fe4e4063e7615122ece083f638e Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 16 Jan 2026 16:13:41 +0500 Subject: [PATCH 15/46] refactor: add validation for RestoreRequest --- .../dbaas/controller/v3/DatabaseBackupV2Controller.java | 2 +- .../cloud/dbaas/dto/backupV2/RestoreRequest.java | 4 ++++ docs/OpenAPI.json | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index 5b98ea0c..df48c199 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -241,7 +241,7 @@ public Response uploadMetadata( @POST public Response restoreBackup(@Parameter(description = "Unique identifier of the backup", required = true) @PathParam("backupName") @NotBlank String backupName, - @RequestBody(description = "Restore request", required = true) + @RequestBody(description = "Restore request") @Valid RestoreRequest restoreRequest, @QueryParam("dryRun") @DefaultValue("false") boolean dryRun) { RestoreResponse response = dbBackupV2Service.restore(backupName, restoreRequest, dryRun); diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java index 10863196..1c761bae 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java @@ -3,6 +3,7 @@ import com.netcracker.cloud.dbaas.enums.ExternalDatabaseStrategy; import com.netcracker.cloud.dbaas.utils.validation.group.RestoreGroup; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; import jakarta.validation.groups.Default; @@ -21,6 +22,7 @@ public class RestoreRequest { "restore-before-prod-update-20251203T1020-4t6S" } ) + @NotBlank private String restoreName; @Schema( description = "Name of the storage backend containing the restore", @@ -29,6 +31,7 @@ public class RestoreRequest { "s3-backend" } ) + @NotBlank private String storageName; @Schema( description = "Path to the restore file in the storage", @@ -37,6 +40,7 @@ public class RestoreRequest { "/backups" } ) + @NotBlank private String blobPath; @Schema( description = "Filter criteria", diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index 7c2e326c..bef8a387 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -3253,6 +3253,7 @@ "examples": [ "restore-before-prod-update-20251203T1020-4t6S" ], + "pattern": "\\S", "description": "Unique identifier of the restore" }, "storageName": { @@ -3260,6 +3261,7 @@ "examples": [ "s3-backend" ], + "pattern": "\\S", "description": "Name of the storage backend containing the restore" }, "blobPath": { @@ -3267,6 +3269,7 @@ "examples": [ "/backups" ], + "pattern": "\\S", "description": "Path to the restore file in the storage" }, "filterCriteria": { @@ -4565,14 +4568,14 @@ ], "requestBody": { "description": "Restore request", - "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RestoreRequest" } } - } + }, + "required": true }, "responses": { "200": { From 8ad5d86789492a2f8db2258d1de3bd9013cb39b9 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 19 Jan 2026 15:23:02 +0500 Subject: [PATCH 16/46] refactor: make databaseKind join conditional --- .../pg/jpa/DatabaseRegistryRepository.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java index b054a392..264eacd5 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/DatabaseRegistryRepository.java @@ -42,11 +42,11 @@ public List findAllByNamespaceAndDatabase_BgVersionNotNull(Str public List findAllDatabasesByFilter(List filters) { StringBuilder q = new StringBuilder( "SELECT cl.* " + - "FROM classifier cl " + - "LEFT JOIN database d ON cl.database_id = d.id " + "FROM classifier cl " ); int index = 0; + boolean needDatabaseJoin = false; List orBlock = new ArrayList<>(); Map params = new HashMap<>(); @@ -68,6 +68,7 @@ public List findAllDatabasesByFilter(List filters) { params.put(typeValues, filter.getDatabaseType().stream().map(DatabaseType::getType).toList().toArray(new String[0])); } if (filter.getDatabaseKind() != null && filter.getDatabaseKind().size() == 1) { + needDatabaseJoin = true; DatabaseKind kind = filter.getDatabaseKind().getFirst(); if (kind == DatabaseKind.CONFIGURATION) { query.add("d.bgversion IS NOT NULL AND d.bgversion <> '' "); @@ -75,13 +76,18 @@ public List findAllDatabasesByFilter(List filters) { query.add("(d.bgversion IS NULL OR d.bgversion = '') "); } } - String block = "(" + String.join(" AND ", query) + ")"; - orBlock.add(block); - index++; + if (!query.isEmpty()) { + String block = "(" + String.join(" AND ", query) + ")"; + orBlock.add(block); + index++; + } } - if (!params.isEmpty()) { + + if (needDatabaseJoin) + q.append("LEFT JOIN database d ON cl.database_id = d.id "); + + if (!orBlock.isEmpty()) q.append("WHERE ").append(String.join(" OR ", orBlock)); - } var query = getEntityManager() .createNativeQuery(q.toString(), DatabaseRegistry.class); From 9f6d293d14500e8b2e2cc408149cd74f48f5624c Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 19 Jan 2026 15:24:00 +0500 Subject: [PATCH 17/46] refactor: make kinds validation flexible --- .../com/netcracker/cloud/dbaas/service/DbBackupV2Service.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index a40ddfb5..4cb6d4df 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -431,7 +431,7 @@ protected Map> getAllDbByFilter(FilterCriteria .flatMap(exclude -> exclude.getDatabaseKind().stream()) .distinct() .count(); - if (uniqKinds == 2) { + if (uniqKinds == DatabaseKind.values().length) { log.warn("No databases matching the filtering criteria were found during the backup"); throw new DbNotFoundException("No databases matching the filtering criteria were found during the backup", Source.builder().build()); } From 371fb8a9645be871ee41c3c55533730b1e608175 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Tue, 20 Jan 2026 14:43:26 +0500 Subject: [PATCH 18/46] feat: add new attribute to Classifier entity --- .../dto/backupV2/ClassifierResponse.java | 1 + .../dbaas/entity/pg/backupV2/Classifier.java | 1 + .../cloud/dbaas/exceptions/ErrorCodes.java | 5 + .../dbaas/service/DbBackupV2Service.java | 103 ++++++++++-------- 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java index e965aa34..9b031e77 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java @@ -12,6 +12,7 @@ @AllArgsConstructor public class ClassifierResponse { private ClassifierType type; + private String previousDatabase; private SortedMap classifier; private SortedMap classifierBeforeMapper; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java index b770bbc6..377814bb 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java @@ -13,6 +13,7 @@ @AllArgsConstructor public class Classifier { private ClassifierType type; + private String previousDatabase; private SortedMap classifier; private SortedMap classifierBeforeMapper; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java index ef079558..acbcf93d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java @@ -242,6 +242,11 @@ public enum ErrorCodes implements ErrorCode { "Digest mismatch", "Digest header mismatch: %s" ), + CORE_DBAAS_4053( + "CORE-DBAAS-4053", + "Operation already running", + "Operation '%s' is already in progress" + ), CORE_DBAAS_7002( "CORE-DBAAS-7002", "trackingId not found", diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index d4d0b7db..825517cf 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -57,6 +57,9 @@ public class DbBackupV2Service { private static final String RESTORE = "restore"; private static final String DATABASE_NAME = "databaseName"; + private static final Integer LOCK_AT_MOST = 2; + private static final Integer LOCK_AT_LEAST = 0; + private final BackupRepository backupRepository; private final RestoreRepository restoreRepository; private final PhysicalDatabasesService physicalDatabasesService; @@ -650,43 +653,40 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, throw new ResourceAlreadyExistsException(restoreName, Source.builder().build()); } - log.info("Start restore for backup {}", backupName); + log.info("Start restore {} for backup {}", restoreName, backupName); Backup backup = getBackupOrThrowException(backupName); BackupStatus backupStatus = backup.getStatus(); if (BackupStatus.COMPLETED != backupStatus) { - log.error("Restore can`t process due to backup status {}", backupStatus); + log.error("Restore {} can`t process due to backup status {}", restoreName, backupStatus); throw new UnprocessableEntityException( - backupName, String.format("restore can`t process due to backup status %s", backupStatus), + restoreName, String.format("restore can`t process due to backup status %s", backupStatus), Source.builder().build()); } LockConfiguration config = new LockConfiguration( Instant.now(), RESTORE, - Duration.ofMinutes(2), - Duration.ofMinutes(0) + Duration.ofMinutes(LOCK_AT_MOST), + Duration.ofMinutes(LOCK_AT_LEAST) ); Optional optLock = lockProvider.lock(config); if (optLock.isEmpty()) - throw new IllegalResourceStateException("restore already running", Source.builder().build()); + throw new OperationAlreadyRunningException("restore", Source.builder().build()); SimpleLock lock = optLock.get(); - boolean unlocked = false; try { if (restoreRepository.countNotCompletedRestores() > 0) - throw new IllegalResourceStateException("another restore is being processed", Source.builder().build()); + throw new OperationAlreadyRunningException("restore", Source.builder().build()); Restore restore = initializeFullRestoreStructure(backup, restoreRequest); if (!dryRun) restoreRepository.save(restore); // unlock method after save restore lock.unlock(); - unlocked = true; - // DryRun on adapters startRestore(restore, true); aggregateRestoreStatus(restore); @@ -698,23 +698,12 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, } if (!dryRun) restoreRepository.save(restore); - else { - restore.getLogicalRestores().forEach(lr -> { - for (RestoreDatabase restoreDatabase : lr.getRestoreDatabases()) { - restoreDatabase.setClassifiers( - findSimilarDbByClassifier(restoreDatabase.getClassifiers(), lr.getType(), true).stream() - .toList() - ); - } - }); - restore.getExternalDatabases().forEach(externalDb -> { - externalDb.setClassifiers(findSimilarDbByClassifier(externalDb.getClassifiers(), externalDb.getType(), true).stream().toList()); - }); - } return mapper.toRestoreResponse(restore); } finally { - if (!unlocked) { + try { lock.unlock(); + } catch (IllegalStateException ex) { + log.debug("Lock is already unlocked", ex); } } } @@ -726,7 +715,7 @@ protected List getAllDbByFilter(List bac new BackupDatabaseDelegate( db, db.getClassifiers().stream() - .map(c -> new Classifier(ClassifierType.NEW, null, c)) + .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) .toList() ) ) @@ -743,7 +732,7 @@ protected List getAllDbByFilter(List bac return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) - .map(c -> new Classifier(ClassifierType.NEW, null, c)) + .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) .toList(); if (filteredClassifiers.isEmpty()) { @@ -865,7 +854,7 @@ protected List validateAndFilterExternalDb(List new BackupExternalDelegate(db, db.getClassifiers().stream() .map(c -> - new Classifier(ClassifierType.NEW, null, c) + new Classifier(ClassifierType.NEW, null, null, c) ) .toList() ) @@ -883,7 +872,7 @@ protected List validateAndFilterExternalDb(List isMatches(filter, namespace, microserviceName, type, false)) && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); }) - .map(c -> new Classifier(ClassifierType.NEW, null, c)) + .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) .toList(); if (filteredClassifiers.isEmpty()) { @@ -906,6 +895,7 @@ private List executeMappingForExternalDb(List updatedClassifiers = db.getClassifiers().stream() .map(classifier -> updateAndValidateClassifier(classifier, mapping, uniqueClassifiers)) .toList(); + updatedClassifiers = findSimilarDbByClassifier(updatedClassifiers, db.getType()).stream().toList(); db.setClassifiers(updatedClassifiers); }) .toList(); @@ -950,7 +940,7 @@ private Classifier updateClassifier(Classifier classifier, Mapping mapping) { updatedClassifier.put(NAMESPACE, targetNamespace); if (targetTenant != null) updatedClassifier.put(TENANT_ID, targetTenant); - if(!targetNamespace.equals(classifier.getClassifierBeforeMapper().get(NAMESPACE))) + if (!targetNamespace.equals(classifier.getClassifierBeforeMapper().get(NAMESPACE))) classifier.setClassifier(updatedClassifier); return classifier; } @@ -1000,7 +990,10 @@ private Map.Entry mapToPhysicalDatabas microserviceName = (String) matchedClassifier.get(MICROSERVICE_NAME); } + // Find similar classifiers String type = db.backupDatabase().getLogicalBackup().getType(); + classifiers = findSimilarDbByClassifier(classifiers, type).stream().toList(); + PhysicalDatabase physicalDatabase = balancingRulesService .applyBalancingRules(type, targetNamespace, microserviceName); @@ -1170,7 +1163,7 @@ protected void checkRestoresAsync() { LockAssert.assertLocked(); List restoresToAggregate = restoreRepository.findRestoresToAggregate(); - log.info("Founded restores to aggregate {}", + log.info("Found restores to aggregate {}", restoresToAggregate.stream().map(Restore::getName).toList()); restoresToAggregate.forEach(this::trackAndAggregateRestore); @@ -1311,14 +1304,15 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { String type = logicalRestore.getType(); log.info("Processing restoreDatabase={}", restoreDatabase.getName()); - Set classifiers = findSimilarDbByClassifier(restoreDatabase.getClassifiers(), type, false); + findAndMarkDatabaseAsOrphan(restoreDatabase.getClassifiers(), type); String adapterId = logicalRestore.getAdapterId(); String physicalDatabaseId = physicalDatabasesService.getByAdapterId(adapterId).getPhysicalDatabaseIdentifier(); List ensuredUsers = dbNameToEnsuredUsers.get(restoreDatabase.getName()); Database newDatabase = createLogicalDatabase( restoreDatabase.getName(), restoreDatabase.getSettings(), - classifiers.stream().map(c -> c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), + restoreDatabase.getClassifiers().stream() + .map(c -> c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), type, false, false, @@ -1331,7 +1325,6 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map { log.info("Processing externalDatabase={}, type={}", externalDatabase.getName(), externalDatabase.getType()); String type = externalDatabase.getType(); - Set classifiers = findSimilarDbByClassifier(externalDatabase.getClassifiers(), type, false); + findAndMarkDatabaseAsOrphan(externalDatabase.getClassifiers(), type); Database newDatabase = createLogicalDatabase( externalDatabase.getName(), null, - classifiers.stream().map(c -> c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), + externalDatabase.getClassifiers().stream() + .map(c -> c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), type, true, true, @@ -1351,7 +1345,6 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map findSimilarDbByClassifier(List classifiers, - String type, boolean dryRun) { + private Set findSimilarDbByClassifier(List classifiers, String type) { Set uniqueClassifiers = new HashSet<>(); classifiers.forEach(classifier -> { SortedMap currClassifier = classifier.getClassifier() != null ? classifier.getClassifier() @@ -1377,32 +1369,40 @@ private Set findSimilarDbByClassifier(List classifiers, if (optionalDatabaseRegistry.isPresent()) { DatabaseRegistry dbRegistry = optionalDatabaseRegistry.get(); - classifier.setType(ClassifierType.REPLACED); Database db = dbRegistry.getDatabase(); + classifier.setType(ClassifierType.REPLACED); + classifier.setPreviousDatabase(db.getName()); log.info("Found existing database {} for classifier {}", db.getId(), currClassifier); Set existClassifiers = db.getDatabaseRegistry().stream() .map(AbstractDatabaseRegistry::getClassifier) .map(TreeMap::new) .map(c -> currClassifier.equals(c) ? classifier - : new Classifier(ClassifierType.TRANSIENT_REPLACED, c, null) + : new Classifier(ClassifierType.TRANSIENT_REPLACED, db.getName(), c, null) ) .collect(Collectors.toSet()); uniqueClassifiers.addAll(existClassifiers); - if(!dryRun) { - dBaaService.markDatabasesAsOrphan(dbRegistry); - log.info("Database {} marked as orphan", db.getId()); - databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); - } } else uniqueClassifiers.add(classifier); }); return uniqueClassifiers; } - private Classifier wrapClassifier(SortedMap classifier) { - return new Classifier(ClassifierType.TRANSIENT_REPLACED, classifier, null); + private void findAndMarkDatabaseAsOrphan(List classifiers, String type) { + classifiers.stream() + .filter(classifier -> ClassifierType.REPLACED == classifier.getType()) + .forEach(classifier -> { + SortedMap currClassifier = classifier.getClassifier() != null ? classifier.getClassifier() + : classifier.getClassifierBeforeMapper(); + databaseRegistryDbaasRepository + .getDatabaseByClassifierAndType(currClassifier, type) + .ifPresent(dbRegistry -> { + dBaaService.markDatabasesAsOrphan(dbRegistry); + log.info("Database {} marked as orphan", dbRegistry.getDatabase().getId()); + databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); + }); + }); } private Database createLogicalDatabase(String dbName, @@ -1607,7 +1607,14 @@ private String extractErrorMessage(Throwable throwable) { } private boolean isFilterEmpty(FilterCriteria filterCriteria) { - return filterCriteria == null || filterCriteria.getFilter() == null || filterCriteria.getFilter().isEmpty(); + if (filterCriteria == null) + return true; + + return isEmpty(filterCriteria.getFilter()) && isEmpty(filterCriteria.getExclude()); + } + + private boolean isEmpty(Collection c) { + return c == null || c.isEmpty(); } private boolean isSingleFilterEmpty(Filter f) { From f405a8e9a04deec99ab502fa1142be0c4277f208 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Tue, 20 Jan 2026 14:54:21 +0500 Subject: [PATCH 19/46] refactor: change restore processing exceptions --- ...perationAlreadyRunningExceptionMapper.java | 22 +++++++++++++++++++ .../OperationAlreadyRunningException.java | 9 ++++++++ 2 files changed, 31 insertions(+) create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/error/OperationAlreadyRunningExceptionMapper.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/error/OperationAlreadyRunningExceptionMapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/error/OperationAlreadyRunningExceptionMapper.java new file mode 100644 index 00000000..b8453a2f --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/error/OperationAlreadyRunningExceptionMapper.java @@ -0,0 +1,22 @@ +package com.netcracker.cloud.dbaas.controller.error; + +import com.netcracker.cloud.dbaas.exceptions.OperationAlreadyRunningException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +import static com.netcracker.cloud.dbaas.controller.error.Utils.buildDefaultResponse; + +@Provider +public class OperationAlreadyRunningExceptionMapper implements ExceptionMapper { + + @Context + UriInfo uriInfo; + + @Override + public Response toResponse(OperationAlreadyRunningException e) { + return buildDefaultResponse(uriInfo, e, Response.Status.CONFLICT); + } +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java new file mode 100644 index 00000000..7ee656b2 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java @@ -0,0 +1,9 @@ +package com.netcracker.cloud.dbaas.exceptions; + +import com.netcracker.cloud.dbaas.dto.Source; + +public class OperationAlreadyRunningException extends ValidationException { + public OperationAlreadyRunningException(String operation, Source source) { + super(ErrorCodes.CORE_DBAAS_4053, ErrorCodes.CORE_DBAAS_4053.getDetail(operation), source); + } +} From 8034dbeee62c5b0cda43f3299cf6466592c77d5d Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Tue, 20 Jan 2026 17:52:54 +0500 Subject: [PATCH 20/46] refactor: rewrite collision catching logic --- .../dbaas/service/DbBackupV2Service.java | 14 +++-- .../dbaas/service/DbBackupV2ServiceTest.java | 59 ++++++++++++++++--- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 825517cf..4e7e9c90 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -1009,23 +1009,27 @@ private Map.Entry mapToPhysicalDatabas return Map.entry(physicalDatabase, new BackupDatabaseDelegate(db.backupDatabase(), classifiers)); } - private Classifier updateAndValidateClassifier( + protected Classifier updateAndValidateClassifier( Classifier classifier, Mapping mapping, Set> uniqueClassifiers) { Classifier updatedClassifier = updateClassifier(classifier, mapping); // To prevent collision during mapping - if (!uniqueClassifiers.add(updatedClassifier.getClassifier())) { + if (!uniqueClassifiers.add(getClassifier(updatedClassifier))) { String msg = String.format( - "Duplicate classifier detected after mapping: classifier='%s', classifierBeforeMapping='%s'. " + - "Ensure all classifiers remain unique after mapping.", - classifier.getClassifier(), classifier.getClassifierBeforeMapper()); + "Duplicate classifier detected after mapping: classifier='%s'. " + + "Ensure all classifiers remain unique after mapping.", getClassifier(updatedClassifier)); log.error(msg); throw new IllegalResourceStateException(msg, Source.builder().build()); } return updatedClassifier; } + private SortedMap getClassifier(Classifier classifier) { + return Optional.ofNullable(classifier.getClassifier()) + .orElseGet(classifier::getClassifierBeforeMapper); + } + protected void startRestore(Restore restore, boolean dryRun) { List logicalRestores = restore.getLogicalRestores(); log.info("Starting requesting adapters to restore startup process: restore={}, dryRun={} logicalRestoreCount={}", diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index a816b869..db9162b5 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import net.javacrumbs.shedlock.core.LockAssert; import net.javacrumbs.shedlock.core.LockProvider; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -1345,9 +1346,9 @@ void restore_mappingInvokeCollision() { SortedMap classifier2 = getClassifier(namespace, microserviceName, anotherTenantId); SortedMap mappedClassifier = getClassifier(mappedNamespace, microserviceName, anotherTenantId); String msg = String.format( - "Resource has illegal state: Duplicate classifier detected after mapping: classifier='%s', classifierBeforeMapping='%s'. " + - "Ensure all classifiers remain unique after mapping.", - mappedClassifier, classifier2); + "Resource has illegal state: Duplicate classifier detected after mapping: " + + "classifier='%s'. Ensure all classifiers remain unique after mapping.", + mappedClassifier); BackupDatabase backupDatabase = getBackupDatabase(dbName, List.of(classifier1, classifier2), false, BackupTaskStatus.COMPLETED, ""); LogicalBackup logicalBackup = getLogicalBackup(logicalBackupName, adapterId, postgresType, List.of(backupDatabase), BackupTaskStatus.COMPLETED, ""); @@ -1576,7 +1577,6 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { anotherTenantId.equals(databaseRegistry5.getClassifier().get(TENANT_ID)) && databaseRegistry5.getClassifier().containsKey(MARKED_FOR_DROP) ); - } @Test @@ -1683,11 +1683,12 @@ void restore_dryRun() { .findAny().orElse(null); assertNotNull(classifier1); assertNotNull(classifier1.getClassifier()); + assertEquals(database.getName(), classifier1.getPreviousDatabase()); assertTrue( mappedNamespace.equals(classifier1.getClassifier().get(NAMESPACE)) && - microserviceName1.equals(classifier1.getClassifier().get(MICROSERVICE_NAME)) && - "tenant".equals(classifier1.getClassifier().get("scope")) && - mappedTenantId.equals(classifier1.getClassifier().get(TENANT_ID)) + microserviceName1.equals(classifier1.getClassifier().get(MICROSERVICE_NAME)) && + "tenant".equals(classifier1.getClassifier().get("scope")) && + mappedTenantId.equals(classifier1.getClassifier().get(TENANT_ID)) ); assertNotNull(classifier1.getClassifierBeforeMapper()); assertTrue( @@ -1702,6 +1703,7 @@ void restore_dryRun() { .findAny().orElse(null); assertNotNull(classifier2); assertNotNull(classifier2.getClassifier()); + assertEquals(database.getName(), classifier2.getPreviousDatabase()); assertTrue( anotherNamespace.equals(classifier2.getClassifier().get(NAMESPACE)) && microserviceName1.equals(classifier2.getClassifier().get(MICROSERVICE_NAME)) && @@ -1719,6 +1721,7 @@ void restore_dryRun() { ClassifierResponse classifier3 = externalDatabaseResponse.getClassifiers().getFirst(); assertNotNull(classifier3); assertEquals(ClassifierType.REPLACED, classifier3.getType()); + assertEquals(externalDb.getName(), classifier3.getPreviousDatabase()); assertNotNull(classifier3.getClassifier()); assertTrue( mappedNamespace.equals(classifier3.getClassifier().get(NAMESPACE)) && @@ -1735,6 +1738,48 @@ void restore_dryRun() { ); } + @Test + void updateAndValidateClassifier() { + String namespace = "test1-namespace"; + String namespaceMapped = "test1-namespace-mapped"; + + List classifiers = getClassifiers(namespace, namespaceMapped); + Mapping mapping = new Mapping(); + mapping.setNamespaces(Map.of(namespace, namespaceMapped)); + + Set> uniqueClassifiers = new HashSet<>(); + IllegalResourceStateException ex = assertThrows( + IllegalResourceStateException.class, + () -> classifiers.forEach(c -> + dbBackupV2Service.updateAndValidateClassifier(c, mapping, uniqueClassifiers) + ) + ); + + assertTrue(ex.getMessage().contains("Duplicate classifier detected after mapping: " + + "classifier='{microserviceName=test1, namespace=test1-namespace-mapped, scope=service}'. " + + "Ensure all classifiers remain unique after mapping")); + } + + private static @NotNull List getClassifiers(String namespace, String namespaceMapped) { + SortedMap classifier1 = new TreeMap<>(); + classifier1.put("microserviceName", "test1"); + classifier1.put("namespace", namespace); + classifier1.put("scope", "service"); + + SortedMap classifier2 = new TreeMap<>(); + classifier2.put("microserviceName", "test1"); + classifier2.put("namespace", namespaceMapped); + classifier2.put("scope", "service"); + + Classifier classifierWrapper1 = new Classifier(); + classifierWrapper1.setClassifierBeforeMapper(classifier1); + + Classifier classifierWrapper2 = new Classifier(); + classifierWrapper2.setClassifierBeforeMapper(classifier2); + + return List.of(classifierWrapper1, classifierWrapper2); + } + @Test void getAllDbByFilter_1() { String namespace1 = "namespace1"; From 8856251c1993da0fbd9a84491d69e19c7efc29ff Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Wed, 21 Jan 2026 12:17:44 +0500 Subject: [PATCH 21/46] chore: reformat code --- .../dbaas/service/DbBackupV2Service.java | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index bc802eff..d59e8f3b 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -1027,9 +1027,24 @@ protected void startRestore(Restore restore, boolean dryRun) { CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); } - private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRestore, List restoreDatabases, String storageName, String blobPath, boolean dryRun) { - return CompletableFuture.supplyAsync(asyncOperations.wrapWithContext(() -> - logicalRestore(logicalRestore.getLogicalRestoreName(), logicalRestore, restoreDatabases, storageName, blobPath, dryRun))) + private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRestore, + List restoreDatabases, + String storageName, + String blobPath, + boolean dryRun + ) { + return CompletableFuture.supplyAsync( + asyncOperations.wrapWithContext( + () -> logicalRestore( + logicalRestore.getLogicalRestoreName(), + logicalRestore, + restoreDatabases, + storageName, + blobPath, + dryRun + ) + ) + ) .thenAccept(response -> refreshLogicalRestoreState(logicalRestore, response)) .exceptionally(throwable -> { @@ -1085,7 +1100,13 @@ private void refreshLogicalRestoreState(LogicalRestore logicalRestore, LogicalRe logicalRestore.getLogicalRestoreName(), logicalRestore.getStatus(), logicalRestore.getErrorMessage()); } - private LogicalRestoreAdapterResponse logicalRestore(String logicalRestoreName, LogicalRestore logicalRestore, List restoreDatabases, String storageName, String blobPath, boolean dryRun) { + private LogicalRestoreAdapterResponse logicalRestore(String logicalRestoreName, + LogicalRestore logicalRestore, + List restoreDatabases, + String storageName, + String blobPath, + boolean dryRun + ) { String logicalBackupName = restoreDatabases.getFirst() .getBackupDatabase() .getLogicalBackup() @@ -1524,10 +1545,10 @@ private void retryRestore(Restore restore) { List> futures = failedLogicalRestores.stream() .map(logicalRestore -> { - List failedRestoreDatabase = logicalRestore.getRestoreDatabases().stream() + List failedRestoreDatabases = logicalRestore.getRestoreDatabases().stream() .filter(db -> RestoreTaskStatus.FAILED == db.getStatus()) .toList(); - return runLogicalRestoreAsync(logicalRestore, failedRestoreDatabase, storageName, blobPath, false); + return runLogicalRestoreAsync(logicalRestore, failedRestoreDatabases, storageName, blobPath, false); } ) .toList(); @@ -1635,23 +1656,25 @@ private T restoreLockWrapper(Supplier action) { LockConfiguration config = new LockConfiguration( Instant.now(), RESTORE, - Duration.ofMinutes(2), - Duration.ofMinutes(0)); + Duration.ofMinutes(LOCK_AT_MOST), + Duration.ofMinutes(LOCK_AT_LEAST)); Optional optLock = lockProvider.lock(config); if (optLock.isEmpty()) - throw new IllegalResourceStateException("restore already running", Source.builder().build()); + throw new OperationAlreadyRunningException("restore", Source.builder().build()); // Start locking action SimpleLock lock = optLock.get(); - boolean unlocked = false; try { if (restoreRepository.countNotCompletedRestores() > 0) - throw new IllegalResourceStateException("another restore is being processed", Source.builder().build()); + throw new OperationAlreadyRunningException("restore", Source.builder().build()); return action.get(); } finally { - if (!unlocked) + try { lock.unlock(); + } catch (IllegalStateException ex) { + log.debug("Lock is already unlocked", ex); + } } } From 1d55eacea42af96c60a10b87d6d83b51d63a013c Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 22 Jan 2026 14:27:29 +0500 Subject: [PATCH 22/46] feat: add validation annotations for dto --- .../cloud/dbaas/dto/backupV2/BackupRequest.java | 2 +- .../cloud/dbaas/dto/backupV2/BackupResponse.java | 14 ++++++++------ .../cloud/dbaas/dto/backupV2/RestoreRequest.java | 2 +- .../cloud/dbaas/dto/backupV2/RestoreResponse.java | 8 +++++++- .../v3/DatabaseBackupV2ControllerTest.java | 4 ++++ 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java index ec6ec712..ac7527af 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java @@ -45,7 +45,7 @@ public class BackupRequest { ) @Valid @NotNull - @ConvertGroup(from = Default.class, to = BackupGroup.class) + @ConvertGroup(to = BackupGroup.class) private FilterCriteria filterCriteria; @Schema( description = "How to handle external databases during backup", diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java index 448a35cd..ffd52995 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java @@ -8,7 +8,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; -import jakarta.validation.groups.Default; import lombok.Data; import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; @@ -21,7 +20,6 @@ @Schema(description = "Response containing backup operation details") public class BackupResponse { - @NotBlank @Schema( description = "Unique identifier of the backup", examples = { @@ -29,8 +27,8 @@ public class BackupResponse { }, required = true ) - private String backupName; @NotBlank + private String backupName; @Schema( description = "Name of the storage backend containing the backup", examples = { @@ -38,8 +36,8 @@ public class BackupResponse { }, required = true ) - private String storageName; @NotBlank + private String storageName; @Schema( description = "Path to the backup file in the storage", examples = { @@ -47,6 +45,7 @@ public class BackupResponse { }, required = true ) + @NotBlank private String blobPath; @Schema( description = "How to handle external databases during backup", @@ -57,20 +56,20 @@ public class BackupResponse { ) @NotNull private ExternalDatabaseStrategy externalDatabaseStrategy; - @NotNull @Schema( description = "Whether external databases were skipped during the backup", examples = { "false" } ) + @NotNull private boolean ignoreNotBackupableDatabases; @Schema( description = "Filter criteria", implementation = FilterCriteria.class ) @Valid - @ConvertGroup(from = Default.class, to = BackupGroup.class) + @ConvertGroup(to = BackupGroup.class) private FilterCriteria filterCriteria; @Schema( @@ -84,6 +83,7 @@ public class BackupResponse { "5" } ) + @NotNull private Integer total; @Schema( description = "Number of databases successfully backed up", @@ -91,6 +91,7 @@ public class BackupResponse { "3" } ) + @NotNull private Integer completed; @Schema( description = "Total size of the backup in bytes", @@ -98,6 +99,7 @@ public class BackupResponse { "1073741824" } ) + @NotNull private Long size; @Schema( description = "Error details if the backup failed", diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java index 1c761bae..1bccd4a1 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java @@ -47,7 +47,7 @@ public class RestoreRequest { implementation = FilterCriteria.class ) @Valid - @ConvertGroup(from = Default.class, to = RestoreGroup.class) + @ConvertGroup(to = RestoreGroup.class) private FilterCriteria filterCriteria; @Schema( description = "Mapping to use for the restore operation", diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java index 9904b20c..376697e3 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java @@ -4,6 +4,7 @@ import com.netcracker.cloud.dbaas.enums.RestoreStatus; import com.netcracker.cloud.dbaas.utils.validation.group.RestoreGroup; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; import jakarta.validation.groups.Default; @@ -25,6 +26,7 @@ public class RestoreResponse { }, required = true ) + @NotBlank private String restoreName; @Schema( description = "Unique identifier of the backup", @@ -32,6 +34,7 @@ public class RestoreResponse { "before-prod-update-20251013T1345-G5s8" } ) + @NotBlank private String backupName; @Schema( description = "Name of the storage backend containing the restore", @@ -39,6 +42,7 @@ public class RestoreResponse { "s3-backend" } ) + @NotBlank private String storageName; @Schema( description = "Path to the restore file in the storage", @@ -61,7 +65,7 @@ public class RestoreResponse { implementation = FilterCriteria.class ) @Valid - @ConvertGroup(from = Default.class, to = RestoreGroup.class) + @ConvertGroup(to = RestoreGroup.class) private FilterCriteria filterCriteria; @Schema( description = "Mapping configuration for the restore", @@ -78,11 +82,13 @@ public class RestoreResponse { description = "Total number of databases being restored", examples = "5" ) + @NotNull private Integer total; @Schema( description = "Completed databases restore operation", examples = "5" ) + @NotNull private Integer completed; @Schema( description = "Aggregated error messages during restore operation", diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java index 5fb38ef9..3c4be407 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java @@ -305,6 +305,10 @@ void uploadMetadata_DigestHeaderAndBodyNotEqual() { backupResponse.setBlobPath("path"); backupResponse.setStorageName("storageName"); backupResponse.setExternalDatabaseStrategy(ExternalDatabaseStrategy.SKIP); + backupResponse.setTotal(0); + backupResponse.setSize(0L); + backupResponse.setCompleted(0); + String expectedDigest = DigestUtil.calculateDigest(backupResponse); String incomingDigest = "SHA-256=abc"; given().auth().preemptive().basic("backup_manager", "backup_manager") From 76cdd399911f729a028b028a8ef6cc1497ec511d Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 22 Jan 2026 15:56:58 +0500 Subject: [PATCH 23/46] chore: rename method, logs, error messages --- .../cloud/dbaas/exceptions/ErrorCodes.java | 4 +- .../dbaas/service/DbBackupV2Service.java | 38 ++++++++++--------- .../v3/DatabaseBackupV2ControllerTest.java | 4 +- .../dbaas/service/DbBackupV2ServiceTest.java | 2 +- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java index acbcf93d..1fe052d2 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java @@ -215,7 +215,7 @@ public enum ErrorCodes implements ErrorCode { CORE_DBAAS_4047( "CORE-DBAAS-4047", "Backup not allowed", - "The backup/restore request can`t be processed. %s" + "The backup/restore request can't be processed. %s" ), CORE_DBAAS_4048( "CORE-DBAAS-4048", @@ -225,7 +225,7 @@ public enum ErrorCodes implements ErrorCode { CORE_DBAAS_4049( "CORE-DBAAS-4049", "Unprocessable resource", - "Resource '%s' can`t be processed: %s" + "Resource '%s' can't be processed: %s" ), CORE_DBAAS_4050( "CORE-DBAAS-4050", diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index d59e8f3b..586fa7ae 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -519,7 +519,7 @@ public BackupResponse getBackupMetadata(String backupName) { if (BackupStatus.COMPLETED != backup.getStatus()) { throw new UnprocessableEntityException(backupName, - String.format("can`t produce metadata for backup in status %s", backup.getStatus()), + String.format("can't produce metadata for backup %s in status %s", backupName, backup.getStatus()), Source.builder().build()); } return mapper.toBackupResponse(backup); @@ -556,7 +556,7 @@ public void uploadBackupMetadata(BackupResponse backupResponse) { } } else { throw new IllegalResourceStateException( - String.format("can`t restore %s backup that not imported", BackupStatus.DELETED), + String.format("can't restore %s backup that not imported", BackupStatus.DELETED), Source.builder().build() ); } @@ -665,7 +665,7 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, log.info("Start restore {} for backup {}", restoreName, backupName); Backup backup = getBackupOrThrowException(backupName); - assertBackupStatusCompleted(backupName, backup.getStatus()); + checkBackupStatusForRestore(backupName, backup.getStatus()); Restore restore = restoreLockWrapper(() -> { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); @@ -807,7 +807,7 @@ protected Restore initializeFullRestoreStructure( protected List validateAndFilterExternalDb(List externalDatabases, ExternalDatabaseStrategy strategy, FilterCriteria filterCriteria) { - if (externalDatabases == null || externalDatabases.isEmpty()) + if (isEmpty(externalDatabases)) return List.of(); String externalNames = externalDatabases.stream() @@ -816,19 +816,24 @@ protected List validateAndFilterExternalDb(List { - log.error("External databases not allowed by strategy={}: {}", ExternalDatabaseStrategy.FAIL, externalNames); + log.error("External databases not allowed by strategy={}. External db names: [{}]", + ExternalDatabaseStrategy.FAIL, externalNames); throw new DatabaseBackupRestoreNotSupportedException( - String.format("External databases not allowed by strategy=%s: %s", ExternalDatabaseStrategy.FAIL, externalNames), + String.format( + "External databases not allowed by strategy=%s. External db names: [%s]", + ExternalDatabaseStrategy.FAIL, externalNames + ), Source.builder().parameter("ExternalDatabaseStrategy").build() ); } case SKIP -> { - log.info("Excluding external databases from restore by strategy={}: external db names {}", + log.info("Excluding external databases from restore by strategy={}. External db names: [{}]", ExternalDatabaseStrategy.SKIP, externalNames); yield List.of(); } case INCLUDE -> { - log.info("Including external databases to restore by strategy: {}", ExternalDatabaseStrategy.INCLUDE); + log.info("Including external databases to restore by strategy={}. External db names: [{}]", + ExternalDatabaseStrategy.INCLUDE, externalNames); if (isFilterEmpty(filterCriteria)) yield mapper.toRestoreExternalDatabases( externalDatabases.stream() @@ -1512,7 +1517,7 @@ public RestoreResponse retryRestore(String restoreName) { Source.builder().build()); } - assertBackupStatusCompleted(restore.getBackup().getName(), restore.getBackup().getStatus()); + checkBackupStatusForRestore(restore.getBackup().getName(), restore.getBackup().getStatus()); return restoreLockWrapper(() -> { retryRestore(restore); @@ -1522,12 +1527,11 @@ public RestoreResponse retryRestore(String restoreName) { }); } - private void assertBackupStatusCompleted(String backupName, BackupStatus status) { - // Check if the status of backup has COMPLETED - if (BackupStatus.COMPLETED != status) { - log.error("Restore can`t process due to backup status {}", status); + private void checkBackupStatusForRestore(String restoreName, BackupStatus status) { + if (status != BackupStatus.COMPLETED) { + log.error("Restore {} can't process due to backup status {}", restoreName, status); throw new UnprocessableEntityException( - backupName, String.format("restore can`t process due to backup status %s", status), + restoreName, String.format("restore can't process due to backup status %s", status), Source.builder().build()); } } @@ -1706,10 +1710,8 @@ private String extractErrorMessage(Throwable throwable) { } private boolean isFilterEmpty(FilterCriteria filterCriteria) { - if (filterCriteria == null) - return true; - - return isEmpty(filterCriteria.getFilter()) && isEmpty(filterCriteria.getExclude()); + return filterCriteria == null + || (isEmpty(filterCriteria.getFilter()) && isEmpty(filterCriteria.getExclude())); } private boolean isEmpty(Collection c) { diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java index 3c4be407..9788eadb 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java @@ -112,7 +112,7 @@ void initiateBackup_databasesCannotBackup_ignoreNotBackupableDatabaseFalse() { .then() .statusCode(422) .body("reason", equalTo("Backup not allowed")) - .body("message", equalTo("The backup/restore request can`t be processed. Backup operation unsupported for databases: " + dbNames)); + .body("message", equalTo("The backup/restore request can't be processed. Backup operation unsupported for databases: " + dbNames)); verify(dbBackupV2Service, times(1)).backup(backupRequest, false); } @@ -356,7 +356,7 @@ void deleteBackup_shouldReturn409_onIllegalState() { .then() .statusCode(422) .body("message", - equalTo(String.format("Resource '%s' can`t be processed: %s", backupName, + equalTo(String.format("Resource '%s' can't be processed: %s", backupName, "has invalid status '" + backupStatus + "'. Only COMPLETED or FAILED backups can be processed."))); } diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index 00b2c854..713187d3 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -3176,7 +3176,7 @@ void uploadBackupMetadata_restoreDeletedBackup_backupNotImported() { () -> dbBackupV2Service.uploadBackupMetadata(backupResponse)); assertEquals( - String.format("Resource has illegal state: can`t restore %s backup that not imported", + String.format("Resource has illegal state: can't restore %s backup that not imported", BackupStatus.DELETED), ex.getDetail()); } From b108e697efe45fc49b549357d40902ed64501ffb Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 22 Jan 2026 16:03:46 +0500 Subject: [PATCH 24/46] refactor: simplify mapping by removing BackupExternalDelegate --- .../dto/backupV2/BackupExternalDelegate.java | 9 -------- .../cloud/dbaas/mapper/BackupV2Mapper.java | 6 ++--- .../dbaas/service/DbBackupV2Service.java | 22 ++++++++----------- 3 files changed, 11 insertions(+), 26 deletions(-) delete mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java deleted file mode 100644 index 34849b3e..00000000 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupExternalDelegate.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.netcracker.cloud.dbaas.entity.dto.backupV2; - -import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; -import com.netcracker.cloud.dbaas.entity.pg.backupV2.BackupExternalDatabase; - -import java.util.List; - -public record BackupExternalDelegate(BackupExternalDatabase backupExternalDatabase, List classifiers) { -} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java index 08ce7e74..a01efa79 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java @@ -2,7 +2,6 @@ import com.netcracker.cloud.dbaas.dto.Source; import com.netcracker.cloud.dbaas.dto.backupV2.*; -import com.netcracker.cloud.dbaas.entity.dto.backupV2.BackupExternalDelegate; import com.netcracker.cloud.dbaas.entity.pg.backupV2.*; import com.netcracker.cloud.dbaas.enums.BackupTaskStatus; import com.netcracker.cloud.dbaas.enums.RestoreTaskStatus; @@ -104,10 +103,9 @@ private static , R extends Enum> R mapStatus( @Mapping(target = "name", source = "backupExternalDatabase.name") @Mapping(target = "type", source = "backupExternalDatabase.type") @Mapping(target = "classifiers", source = "classifiers") - RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDelegate backupExternalDelegate); - - List toRestoreExternalDatabases(List backupExternalDelegates); + RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDatabase backupExternalDatabase, List classifiers); ClassifierResponse toClassifierResponse(Classifier classifier); + List toClassifierResponse(List classifiers); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 586fa7ae..aff320f8 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -835,18 +835,14 @@ protected List validateAndFilterExternalDb(List - new BackupExternalDelegate(db, db.getClassifiers().stream() - .map(c -> - new Classifier(ClassifierType.NEW, null, null, c) - ) - .toList() - ) - ) - .toList() - ); + yield externalDatabases.stream() + .map(db -> mapper.toRestoreExternalDatabase( + db, + db.getClassifiers().stream() + .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) + .toList() + )) + .toList(); yield externalDatabases.stream() .map(db -> { @@ -865,7 +861,7 @@ protected List validateAndFilterExternalDb(List Date: Mon, 26 Jan 2026 12:22:29 +0500 Subject: [PATCH 25/46] feat: prevent restore empty databases --- .../cloud/dbaas/service/DbBackupV2Service.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index aff320f8..daf762fa 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -702,7 +702,7 @@ protected List getAllDbByFilter(List bac ) .toList(); - List databaseDelegateList = backupDatabasesToFilter.stream() + return backupDatabasesToFilter.stream() .map(db -> { List filteredClassifiers = db.getClassifiers().stream() .filter(classifier -> { @@ -724,13 +724,6 @@ protected List getAllDbByFilter(List bac }) .filter(Objects::nonNull) .toList(); - - - if (databaseDelegateList.isEmpty()) { - log.warn("During restore databases that match filterCriteria not found"); - throw new DbNotFoundException("Databases that match filterCriteria not found", Source.builder().build()); - } - return databaseDelegateList; } protected Restore initializeFullRestoreStructure( @@ -754,6 +747,10 @@ protected Restore initializeFullRestoreStructure( .toList(), restoreRequest.getFilterCriteria()); + if (externalDatabases.isEmpty() && backupDatabases.isEmpty()) { + log.warn("Databases that match filterCriteria during restore not found"); + throw new DbNotFoundException("Databases that match filterCriteria not found", Source.builder().build()); + } // Group BackupDatabase by updated adapters Map> groupedByTypeAndAdapter = From 5862147597000d0e4411d56e9fba91de27b71efb Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Wed, 28 Jan 2026 19:46:44 +0500 Subject: [PATCH 26/46] fix: adding missed statuses for OpenApi --- .../v3/DatabaseBackupV2Controller.java | 10 +++-- docs/OpenAPI.json | 43 ++++++++++++++++--- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index df48c199..8bf429cf 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -63,7 +63,7 @@ public DatabaseBackupV2Controller(DbBackupV2Service dbBackupV2Service) { content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "409", description = "The request could not be completed due to a conflict with the current state of the resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), - @APIResponse(responseCode = "422", description = "The request was accepted, but the server could`t process due to incompatible resource", + @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @@ -159,7 +159,7 @@ public Response getBackupStatus(@Parameter(description = "Unique identifier of t @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), @APIResponse(responseCode = "404", description = "The requested resource could not be found", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), - @APIResponse(responseCode = "422", description = "The request was accepted, but the server could`t process due to incompatible resource", + @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))) @@ -230,7 +230,7 @@ public Response uploadMetadata( content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "409", description = "The request could not be completed due to a conflict with the current state of the resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), - @APIResponse(responseCode = "422", description = "The request was accepted, but the server could`t process due to incompatible resource", + @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @@ -314,6 +314,10 @@ public Response getRestoreStatus(@Parameter(description = "Unique identifier of @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), @APIResponse(responseCode = "404", description = "The requested resource could not be found", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), + @APIResponse(responseCode = "409", description = "The request could not be completed due to a conflict with the current state of the resource", + content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), + @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", + content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))) }) diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index bef8a387..d1e23193 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -381,7 +381,10 @@ "blobPath", "externalDatabaseStrategy", "ignoreNotBackupableDatabases", - "status" + "status", + "total", + "completed", + "size" ], "description": "Response containing backup operation details", "properties": { @@ -646,6 +649,9 @@ "type": { "$ref": "#/components/schemas/ClassifierType" }, + "previousDatabase": { + "type": "string" + }, "classifier": { "type": "object", "additionalProperties": {} @@ -3347,8 +3353,12 @@ "type": "object", "required": [ "restoreName", + "backupName", + "storageName", "externalDatabaseStrategy", - "status" + "status", + "total", + "completed" ], "description": "Response containing the restore operation details", "properties": { @@ -3357,6 +3367,7 @@ "examples": [ "restore-before-prod-update-20251203T1020-4t6S" ], + "pattern": "\\S", "description": "Unique identifier of the restore" }, "backupName": { @@ -3364,6 +3375,7 @@ "examples": [ "before-prod-update-20251013T1345-G5s8" ], + "pattern": "\\S", "description": "Unique identifier of the backup" }, "storageName": { @@ -3371,6 +3383,7 @@ "examples": [ "s3-backend" ], + "pattern": "\\S", "description": "Name of the storage backend containing the restore" }, "blobPath": { @@ -4281,7 +4294,7 @@ } }, "422": { - "description": "The request was accepted, but the server could`t process due to incompatible resource", + "description": "The request was accepted, but the server couldn\u0027t process due to incompatible resource", "content": { "application/json": { "schema": { @@ -4510,7 +4523,7 @@ } }, "422": { - "description": "The request was accepted, but the server could`t process due to incompatible resource", + "description": "The request was accepted, but the server couldn\u0027t process due to incompatible resource", "content": { "application/json": { "schema": { @@ -4635,7 +4648,7 @@ } }, "422": { - "description": "The request was accepted, but the server could`t process due to incompatible resource", + "description": "The request was accepted, but the server couldn\u0027t process due to incompatible resource", "content": { "application/json": { "schema": { @@ -4989,6 +5002,26 @@ } } }, + "409": { + "description": "The request could not be completed due to a conflict with the current state of the resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TmfErrorResponse" + } + } + } + }, + "422": { + "description": "The request was accepted, but the server couldn\u0027t process due to incompatible resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TmfErrorResponse" + } + } + } + }, "500": { "description": "An unexpected error occurred on the server", "content": { From b85d622517a89148fb7427e689db76a705a89289 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Wed, 28 Jan 2026 20:29:26 +0500 Subject: [PATCH 27/46] refactor: separate mapping, enriching classifiers operation --- .../entity/dto/backupV2/AdapterBackupKey.java | 4 + .../dbaas/service/DbBackupV2Service.java | 307 +++++++++-------- .../dbaas/service/DbBackupV2ServiceTest.java | 320 +++++++++++++++++- 3 files changed, 480 insertions(+), 151 deletions(-) create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/AdapterBackupKey.java diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/AdapterBackupKey.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/AdapterBackupKey.java new file mode 100644 index 00000000..3d598ea8 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/AdapterBackupKey.java @@ -0,0 +1,4 @@ +package com.netcracker.cloud.dbaas.entity.dto.backupV2; + +public record AdapterBackupKey(String adapterId, String logicalBackupName) { +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index daf762fa..d848347b 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -679,7 +679,6 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, aggregateRestoreStatus(restore); if (!dryRun && RestoreStatus.FAILED != restore.getStatus()) { // Real run on adapters - restore = getRestoreOrThrowException(restoreName); startRestore(restore, false); aggregateRestoreStatus(restore); } @@ -696,7 +695,7 @@ protected List getAllDbByFilter(List bac new BackupDatabaseDelegate( db, db.getClassifiers().stream() - .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) + .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList() ) ) @@ -713,7 +712,7 @@ protected List getAllDbByFilter(List bac return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) && filterCriteria.getExclude().stream().filter(exclude -> !isEmpty(exclude)).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) - .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) + .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList(); if (filteredClassifiers.isEmpty()) { @@ -730,41 +729,48 @@ protected Restore initializeFullRestoreStructure( Backup backup, RestoreRequest restoreRequest ) { - // Apply ExternalDatabaseStrategy to external databases, filter by FilterCriteria - List externalDatabases = validateAndFilterExternalDb( + // Apply ExternalDatabaseStrategy to external databases and filter by FilterCriteria + List filteredExternalDbs = validateAndFilterExternalDb( backup.getExternalDatabases(), restoreRequest.getExternalDatabaseStrategy(), restoreRequest.getFilterCriteria()); - // MappingEntity classifiers of externalDb - if (restoreRequest.getMapping() != null) - externalDatabases = executeMappingForExternalDb(externalDatabases, restoreRequest.getMapping()); - - // Filtering classifiers - List backupDatabases = getAllDbByFilter( + // Filter internal database classifiers + List filteredBackupDatabases = getAllDbByFilter( backup.getLogicalBackups().stream() .flatMap(logicalBackup -> logicalBackup.getBackupDatabases().stream()) .toList(), restoreRequest.getFilterCriteria()); - if (externalDatabases.isEmpty() && backupDatabases.isEmpty()) { + if (filteredExternalDbs.isEmpty() && filteredBackupDatabases.isEmpty()) { log.warn("Databases that match filterCriteria during restore not found"); throw new DbNotFoundException("Databases that match filterCriteria not found", Source.builder().build()); } - // Group BackupDatabase by updated adapters - Map> groupedByTypeAndAdapter = - groupBackupDatabasesByTypeAndAdapter(backupDatabases, restoreRequest.getMapping()); + // Mapping classifiers + List mappedExternalDbs = + executeMappingForExternalDb(filteredExternalDbs, restoreRequest.getMapping()); + List mappedBackupDatabases = + applyMappingToBackupDatabases(filteredBackupDatabases, restoreRequest.getMapping()); + + // Enrich classifiers + List enrichedBackupClassifiers = applySimilarDbClassifiers(mappedBackupDatabases); + // Group BackupDatabases by updated adapter and logicalBackupName + Map> groupedByTypeAndAdapter = + groupBackupDatabasesByLogicalBackupNameAndAdapter(enrichedBackupClassifiers); + + checkForCollision(mappedExternalDbs, groupedByTypeAndAdapter.values().stream().flatMap(List::stream).toList()); log.info("Initializing restore structure: restoreName={}, backupName={}", restoreRequest.getRestoreName(), backup.getName()); + // Build logicalRestores for each new adapter List logicalRestores = groupedByTypeAndAdapter.entrySet().stream() .map(entry -> { LogicalRestore logicalRestore = new LogicalRestore(); - logicalRestore.setType(entry.getKey().getType()); - logicalRestore.setAdapterId(entry.getKey().getAdapter().getAdapterId()); + logicalRestore.setType(entry.getValue().getFirst().backupDatabase().getLogicalBackup().getType()); + logicalRestore.setAdapterId(entry.getKey().adapterId()); List restoreDatabases = createRestoreDatabases(entry.getValue()); @@ -784,26 +790,99 @@ protected Restore initializeFullRestoreStructure( restore.setBlobPath(restoreRequest.getBlobPath()); restore.setLogicalRestores(new ArrayList<>(logicalRestores)); restore.setExternalDatabaseStrategy(restoreRequest.getExternalDatabaseStrategy()); - restore.setExternalDatabases(externalDatabases); + restore.setExternalDatabases(mappedExternalDbs); restore.setMapping(mapper.toMappingEntity(restoreRequest.getMapping())); restore.setFilterCriteria(mapper.toFilterCriteriaEntity(restoreRequest.getFilterCriteria())); // set up relation logicalRestores.forEach(lr -> lr.setRestore(restore)); - externalDatabases.forEach(db -> db.setRestore(restore)); + mappedExternalDbs.forEach(db -> db.setRestore(restore)); int totalDatabases = logicalRestores.stream() .mapToInt(lr -> lr.getRestoreDatabases().size()) .sum(); log.info("Restore structure initialized: restoreName={}, logicalRestores={}, restoreDatabases={}, externalDatabases={}", - restore.getName(), logicalRestores.size(), totalDatabases, externalDatabases.size()); + restore.getName(), logicalRestores.size(), totalDatabases, mappedExternalDbs.size()); return restore; } - protected List validateAndFilterExternalDb(List externalDatabases, - ExternalDatabaseStrategy strategy, - FilterCriteria filterCriteria) { + private List applyMappingToBackupDatabases(List backupDatabases, Mapping mapping) { + return backupDatabases.stream() + .map(db -> + new BackupDatabaseDelegate(db.backupDatabase(), applyMapping(db.classifiers(), mapping)) + ).toList(); + } + + protected List applyMapping(List classifiers, Mapping mapping) { + List mappedClassifiers = new ArrayList<>(); + + for (Classifier classifier : classifiers) { + SortedMap sourceClassifier = classifier.getClassifierBeforeMapper(); + SortedMap mappedClassifier = new TreeMap<>(sourceClassifier); + if (mapping != null) { + String targetNamespace = getValue(mapping.getNamespaces(), (String) sourceClassifier.get(NAMESPACE)); + String targetTenant = (String) sourceClassifier.get(TENANT_ID); + mappedClassifier.put(NAMESPACE, targetNamespace); + + if (targetTenant != null) { + targetTenant = getValue(mapping.getTenants(), targetTenant); + mappedClassifier.put(TENANT_ID, targetTenant); + } + } + classifier.setClassifier(mappedClassifier); + mappedClassifiers.add(classifier); + } + return mappedClassifiers; + } + + private List applySimilarDbClassifiers(List backupDatabases) { + return backupDatabases.stream() + .map(db -> { + String type = db.backupDatabase().getLogicalBackup().getType(); + List updatedClassifiers = findSimilarDbByClassifier(db.classifiers(), type).stream().toList(); + return new BackupDatabaseDelegate(db.backupDatabase(), updatedClassifiers); + }).toList(); + } + + private void checkForCollision(List externalDatabases, List backupDatabases) { + Set> uniqueClassifiers = new HashSet<>(); + List duplicateClassifiers = new ArrayList<>(); + + for (RestoreExternalDatabase externalDatabase : externalDatabases) { + for (Classifier classifier : externalDatabase.getClassifiers()) + collectDuplicateClassifiers(classifier, uniqueClassifiers, duplicateClassifiers); + } + + for (BackupDatabaseDelegate internalDatabase : backupDatabases) { + for (Classifier classifier : internalDatabase.classifiers()) + collectDuplicateClassifiers(classifier, uniqueClassifiers, duplicateClassifiers); + } + + if (!duplicateClassifiers.isEmpty()) { + String msg = String.format( + "Duplicate classifiers detected after mapping. Duplicate classifiers=%s. " + + "Ensure all classifiers remain unique after mapping.", duplicateClassifiers); + log.error(msg); + throw new IllegalResourceStateException(msg, Source.builder().build()); + } + } + + private void collectDuplicateClassifiers( + Classifier classifier, + Set> uniqueClassifiers, + List duplicateClassifiers + ) { + SortedMap c = classifier.getClassifier(); + if (!uniqueClassifiers.add(c)) { + duplicateClassifiers.add(classifier); + } + } + + protected List validateAndFilterExternalDb + (List externalDatabases, + ExternalDatabaseStrategy strategy, + FilterCriteria filterCriteria) { if (isEmpty(externalDatabases)) return List.of(); @@ -836,7 +915,7 @@ protected List validateAndFilterExternalDb(List mapper.toRestoreExternalDatabase( db, db.getClassifiers().stream() - .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) + .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList() )) .toList(); @@ -851,7 +930,7 @@ protected List validateAndFilterExternalDb(List isMatches(filter, namespace, microserviceName, type, false)) && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); }) - .map(c -> new Classifier(ClassifierType.NEW, null, null, c)) + .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList(); if (filteredClassifiers.isEmpty()) { @@ -866,18 +945,19 @@ protected List validateAndFilterExternalDb(List executeMappingForExternalDb(List externalDatabases, - Mapping mapping) { - Set> uniqueClassifiers = new HashSet<>(); - return externalDatabases.stream() - .peek(db -> { - List updatedClassifiers = db.getClassifiers().stream() - .map(classifier -> updateAndValidateClassifier(classifier, mapping, uniqueClassifiers)) - .toList(); - updatedClassifiers = findSimilarDbByClassifier(updatedClassifiers, db.getType()).stream().toList(); - db.setClassifiers(updatedClassifiers); - }) - .toList(); + private List executeMappingForExternalDb( + List externalDatabases, + Mapping mapping + ) { + List updatedExternals = new ArrayList<>(); + + for (RestoreExternalDatabase externalDatabase : externalDatabases) { + List mappedClassifiers = applyMapping(externalDatabase.getClassifiers(), mapping); + List enrichedClassifiers = findSimilarDbByClassifier(mappedClassifiers, externalDatabase.getType()).stream().toList(); + externalDatabase.setClassifiers(enrichedClassifiers); + updatedExternals.add(externalDatabase); + } + return updatedExternals; } private List createRestoreDatabases( @@ -887,7 +967,7 @@ private List createRestoreDatabases( .map(delegatedBackupDatabase -> { BackupDatabase backupDatabase = delegatedBackupDatabase.backupDatabase(); List classifiers = delegatedBackupDatabase.classifiers(); - String namespace = classifiers.getFirst().getClassifier() != null ? (String) classifiers.getFirst().getClassifier().get(NAMESPACE) : (String) classifiers.getFirst().getClassifierBeforeMapper().get(NAMESPACE); + String namespace = (String) classifiers.getFirst().getClassifier().get(NAMESPACE); String bgVersion = null; if (backupDatabase.isConfigurational()) { Optional bgNamespace = bgNamespaceRepository.findBgNamespaceByNamespace(namespace); @@ -911,19 +991,6 @@ private List createRestoreDatabases( .toList(); } - private Classifier updateClassifier(Classifier classifier, Mapping mapping) { - String targetNamespace = getValue(mapping.getNamespaces(), (String) classifier.getClassifierBeforeMapper().get(NAMESPACE)); - String targetTenant = getValue(mapping.getTenants(), (String) classifier.getClassifierBeforeMapper().get(TENANT_ID)); - - SortedMap updatedClassifier = new TreeMap<>(classifier.getClassifierBeforeMapper()); - updatedClassifier.put(NAMESPACE, targetNamespace); - if (targetTenant != null) - updatedClassifier.put(TENANT_ID, targetTenant); - if (!targetNamespace.equals(classifier.getClassifierBeforeMapper().get(NAMESPACE))) - classifier.setClassifier(updatedClassifier); - return classifier; - } - private String getValue(Map map, String oldValue) { if (map == null || map.isEmpty()) { return oldValue; @@ -931,53 +998,51 @@ private String getValue(Map map, String oldValue) { return map.getOrDefault(oldValue, oldValue); } - private Map> groupBackupDatabasesByTypeAndAdapter( - List backupDatabases, - Mapping mapping) { - Set> uniqueClassifiers = new HashSet<>(); + private Map> groupBackupDatabasesByLogicalBackupNameAndAdapter( + List backupDatabases + ) { return backupDatabases.stream() - .map(db -> mapToPhysicalDatabaseEntry(db, mapping, uniqueClassifiers)) + .map(this::mapToAdapterBackupKeyEntry) .collect(Collectors.groupingBy( Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList()) )); } - private Map.Entry mapToPhysicalDatabaseEntry(BackupDatabaseDelegate db, - Mapping mapping, - Set> uniqueClassifiers) { - List classifiers = db.classifiers(); - SortedMap firstClassifier = classifiers.getFirst().getClassifierBeforeMapper(); - String targetNamespace = (String) firstClassifier.get(NAMESPACE); - String microserviceName = (String) firstClassifier.get(MICROSERVICE_NAME); - - // Mapping classifiers - if (mapping != null && mapping.getNamespaces() != null) { - classifiers = db.classifiers().stream() - .map(classifier -> updateAndValidateClassifier(classifier, mapping, uniqueClassifiers)) - .toList(); + private Map.Entry mapToAdapterBackupKeyEntry( + BackupDatabaseDelegate backupDatabaseDelegate + ) { + List classifiers = backupDatabaseDelegate.classifiers(); + String type = backupDatabaseDelegate.backupDatabase().getLogicalBackup().getType(); + String logicalBackupName = backupDatabaseDelegate.backupDatabase().getLogicalBackup().getLogicalBackupName(); - // Find the first classifier whose namespace exists in the mapping - SortedMap matchedClassifier = classifiers.stream() - .map(Classifier::getClassifierBeforeMapper) - .filter(c -> mapping.getNamespaces().containsKey((String) c.get(NAMESPACE))) + Optional adapterIdFromClassifier = classifiers.stream() + .filter(c -> ClassifierType.REPLACED == c.getType() || ClassifierType.TRANSIENT_REPLACED == c.getType()) + .map(Classifier::getClassifier) + .filter(Objects::nonNull) + .map(c -> databaseRegistryDbaasRepository.getDatabaseByClassifierAndType(c, type)) + .filter(Optional::isPresent) + .map(opt -> opt.get().getDatabase().getAdapterId()) + .findFirst(); + + String adapterId; + if (adapterIdFromClassifier.isPresent()) { + adapterId = adapterIdFromClassifier.get(); + } else { + SortedMap firstNewClassifier = classifiers.stream() + .filter(c -> ClassifierType.NEW == c.getType()) + .map(Classifier::getClassifier) + .filter(Objects::nonNull) .findFirst() - .orElse(classifiers.getFirst().getClassifierBeforeMapper()); - - String oldNamespace = (String) matchedClassifier.get(NAMESPACE); - targetNamespace = mapping.getNamespaces().getOrDefault(oldNamespace, oldNamespace); - microserviceName = (String) matchedClassifier.get(MICROSERVICE_NAME); + .orElseGet(() -> classifiers.getFirst().getClassifier()); + String targetNamespace = (String) firstNewClassifier.get(NAMESPACE); + String microserviceName = (String) firstNewClassifier.get(MICROSERVICE_NAME); + PhysicalDatabase physicalDatabase = balancingRulesService + .applyBalancingRules(type, targetNamespace, microserviceName); + adapterId = physicalDatabase.getAdapter().getAdapterId(); } - // Find similar classifiers - String type = db.backupDatabase().getLogicalBackup().getType(); - classifiers = findSimilarDbByClassifier(classifiers, type).stream().toList(); - - PhysicalDatabase physicalDatabase = balancingRulesService - .applyBalancingRules(type, targetNamespace, microserviceName); - // Checking adapter support backup restore - String adapterId = physicalDatabase.getAdapter().getAdapterId(); DbaasAdapter adapter = physicalDatabasesService.getAdapterById(adapterId); if (!isBackupRestoreSupported(adapter)) { throw new DatabaseBackupRestoreNotSupportedException( @@ -985,28 +1050,8 @@ private Map.Entry mapToPhysicalDatabas Source.builder().build()); } - return Map.entry(physicalDatabase, new BackupDatabaseDelegate(db.backupDatabase(), classifiers)); - } - - protected Classifier updateAndValidateClassifier( - Classifier classifier, - Mapping mapping, - Set> uniqueClassifiers) { - Classifier updatedClassifier = updateClassifier(classifier, mapping); - // To prevent collision during mapping - if (!uniqueClassifiers.add(getClassifier(updatedClassifier))) { - String msg = String.format( - "Duplicate classifier detected after mapping: classifier='%s'. " + - "Ensure all classifiers remain unique after mapping.", getClassifier(updatedClassifier)); - log.error(msg); - throw new IllegalResourceStateException(msg, Source.builder().build()); - } - return updatedClassifier; - } - - private SortedMap getClassifier(Classifier classifier) { - return Optional.ofNullable(classifier.getClassifier()) - .orElseGet(classifier::getClassifierBeforeMapper); + return Map.entry(new AdapterBackupKey(adapterId, logicalBackupName), + new BackupDatabaseDelegate(backupDatabaseDelegate.backupDatabase(), classifiers)); } protected void startRestore(Restore restore, boolean dryRun) { @@ -1077,7 +1122,7 @@ private void refreshLogicalRestoreState(LogicalRestore logicalRestore, LogicalRe restoreDatabase.setCreationTime(db.getCreationTime()); if (!restoreDatabase.getName().equals(db.getDatabaseName())) { restoreDatabase.setName(db.getDatabaseName()); - log.debug("For restore={} backup database updated: old name={}, new name={}", + log.debug("For restore={} restoreDatabase updated: old name={}, new name={}", logicalRestore.getRestore().getName(), db.getPreviousDatabaseName(), restoreDatabase.getName()); } } else { @@ -1121,12 +1166,12 @@ private List> buildRestoreDatabases(List re return restoreDatabases.stream() .map(restoreDatabase -> { String namespace = restoreDatabase.getClassifiers().stream() - .map(c -> c.getClassifier() != null ? (String) c.getClassifier().get(NAMESPACE) : (String) c.getClassifierBeforeMapper().get(NAMESPACE)) + .map(c -> (String) c.getClassifier().get(NAMESPACE)) .findFirst() .orElse(""); String microserviceName = restoreDatabase.getClassifiers().stream() - .map(c -> c.getClassifier() != null ? (String) c.getClassifier().get(MICROSERVICE_NAME) : (String) c.getClassifierBeforeMapper().get(MICROSERVICE_NAME)) + .map(c -> (String) c.getClassifier().get(MICROSERVICE_NAME)) .findFirst() .orElse(""); @@ -1177,12 +1222,14 @@ protected void checkRestoresAsync() { log.info("Found restores to aggregate {}", restoresToAggregate.stream().map(Restore::getName).toList()); - restoresToAggregate.forEach(this::trackAndAggregateRestore); restoresToAggregate.forEach(restore -> { - if (!Objects.equals(restore.getTotal(), restore.getCompleted()) && RestoreStatus.COMPLETED != restore.getStatus()) { + trackAndAggregateRestore(restore); + + if (RestoreStatus.COMPLETED != restore.getStatus() || !Objects.equals(restore.getTotal(), restore.getCompleted())) { restoreRepository.save(restore); return; } + Map> dbNameToEnsuredUsers = restore.getLogicalRestores().stream() .flatMap(lr -> lr.getRestoreDatabases().stream() .map(rd -> Map.entry( @@ -1190,7 +1237,7 @@ protected void checkRestoresAsync() { ensureUsers(lr.getAdapterId(), rd.getName(), rd.getUsers()) )) ) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); initializeLogicalDatabasesFromRestore(restore, dbNameToEnsuredUsers); restoreRepository.save(restore); @@ -1311,7 +1358,8 @@ private void aggregateRestoreStatus(Restore restore) { } @Transactional - protected void initializeLogicalDatabasesFromRestore(Restore restore, Map> dbNameToEnsuredUsers) { + protected void initializeLogicalDatabasesFromRestore(Restore restore, + Map> dbNameToEnsuredUsers) { log.info("Start creating logicalDatabases from restore {}", restore.getName()); try { // Creating LogicalDb based logicalRestores @@ -1328,7 +1376,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), + .map(Classifier::getClassifier).collect(Collectors.toSet()), type, false, false, @@ -1353,7 +1401,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map c.getClassifier() != null ? c.getClassifier() : c.getClassifierBeforeMapper()).collect(Collectors.toSet()), + .map(Classifier::getClassifier).collect(Collectors.toSet()), type, true, true, @@ -1376,8 +1424,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, Map findSimilarDbByClassifier(List classifiers, String type) { Set uniqueClassifiers = new HashSet<>(); classifiers.forEach(classifier -> { - SortedMap currClassifier = classifier.getClassifier() != null ? classifier.getClassifier() - : classifier.getClassifierBeforeMapper(); + SortedMap currClassifier = classifier.getClassifier(); log.debug("Classifier candidate: {}", currClassifier); Optional optionalDatabaseRegistry = databaseRegistryDbaasRepository @@ -1391,6 +1438,7 @@ private Set findSimilarDbByClassifier(List classifiers, log.info("Found existing database {} for classifier {}", db.getId(), currClassifier); Set existClassifiers = db.getDatabaseRegistry().stream() .map(AbstractDatabaseRegistry::getClassifier) + .filter(c -> !c.containsKey(MARKED_FOR_DROP)) // TODO .map(TreeMap::new) .map(c -> currClassifier.equals(c) ? classifier @@ -1409,8 +1457,7 @@ private void findAndMarkDatabaseAsOrphan(List classifiers, String ty classifiers.stream() .filter(classifier -> ClassifierType.REPLACED == classifier.getType()) .forEach(classifier -> { - SortedMap currClassifier = classifier.getClassifier() != null ? classifier.getClassifier() - : classifier.getClassifierBeforeMapper(); + SortedMap currClassifier = classifier.getClassifier(); databaseRegistryDbaasRepository .getDatabaseByClassifierAndType(currClassifier, type) .ifPresent(dbRegistry -> { @@ -1510,8 +1557,6 @@ public RestoreResponse retryRestore(String restoreName) { Source.builder().build()); } - checkBackupStatusForRestore(restore.getBackup().getName(), restore.getBackup().getStatus()); - return restoreLockWrapper(() -> { retryRestore(restore); aggregateRestoreStatus(restore); @@ -1553,9 +1598,10 @@ private void retryRestore(Restore restore) { CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); } - protected Map> validateAndFilterDatabasesForBackup(Map> databasesForBackup, - boolean ignoreNotBackupableDatabases, - ExternalDatabaseStrategy strategy) { + protected Map> validateAndFilterDatabasesForBackup( + Map> databasesForBackup, + boolean ignoreNotBackupableDatabases, + ExternalDatabaseStrategy strategy) { Map>> partitioned = databasesForBackup.entrySet().stream() .collect(Collectors.groupingBy(entry -> @@ -1649,7 +1695,7 @@ private Restore getRestoreOrThrowException(String restoreName) { } private T restoreLockWrapper(Supplier action) { - // Only one retry restore operation able to process + // Only one restore operation able to process LockConfiguration config = new LockConfiguration( Instant.now(), RESTORE, @@ -1711,13 +1757,6 @@ private boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } - private boolean isSingleFilterEmpty(Filter f) { - return (f.getNamespace() == null || f.getNamespace().isEmpty()) - && (f.getMicroserviceName() == null || f.getMicroserviceName().isEmpty()) - && (f.getDatabaseType() == null || f.getDatabaseType().isEmpty()) - && (f.getDatabaseKind() == null || f.getDatabaseKind().isEmpty()); - } - private static , R extends Enum> R aggregateStatus( Set statusSet, Function taskStatusGetter, diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index 713187d3..b8234650 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -1345,10 +1345,7 @@ void restore_mappingInvokeCollision() { SortedMap classifier1 = getClassifier(namespace, microserviceName, tenantId); SortedMap classifier2 = getClassifier(namespace, microserviceName, anotherTenantId); SortedMap mappedClassifier = getClassifier(mappedNamespace, microserviceName, anotherTenantId); - String msg = String.format( - "Resource has illegal state: Duplicate classifier detected after mapping: " + - "classifier='%s'. Ensure all classifiers remain unique after mapping.", - mappedClassifier); + BackupDatabase backupDatabase = getBackupDatabase(dbName, List.of(classifier1, classifier2), false, BackupTaskStatus.COMPLETED, ""); LogicalBackup logicalBackup = getLogicalBackup(logicalBackupName, adapterId, postgresType, List.of(backupDatabase), BackupTaskStatus.COMPLETED, ""); @@ -1377,7 +1374,8 @@ void restore_mappingInvokeCollision() { false )); - assertEquals(msg, ex.getDetail()); + String msg = mappedClassifier.toString(); + assertTrue(ex.getDetail().contains(msg)); } @Test @@ -1579,6 +1577,196 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { ); } + @Test + void restore_testChoosingAdapterBasedClassifierType() { + String restoreName = "restoreName"; + String backupName = "backupName"; + String logicalBackupName1 = "logicalBackupName"; + String logicalBackupName2 = "logicalBackupName2"; + String logicalRestoreName1 = "logicalRestoreName"; + String logicalRestoreName2 = "logicalRestoreName2"; + + String dbName1 = "dbName"; + String dbName2 = "dbName2"; + String newName1 = "newName"; + String newName2 = "newName2"; + String adapterId1 = "adapterId1"; + String adapterId2 = "adapterId2"; + + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String mappedNamespace = "mappedNamespace"; + String anotherNamespace = "anotherNamespace"; + + String microserviceName1 = "microserviceName1"; + String microserviceName2 = "microserviceName2"; + + String tenantId = "tenantId"; + String anotherTenantId = "tenantId"; + String mappedTenantId = "mappedTenantId"; + String postgresType = "postgresql"; + + // Database with registries that exists in another namespace + Database database = getDatabase(adapterId1, newName1, false, false, null); + DatabaseRegistry registry1 = getDatabaseRegistry(database, mappedNamespace, microserviceName1, mappedTenantId, postgresType); + DatabaseRegistry registry2 = getDatabaseRegistry(database, anotherNamespace, microserviceName1, anotherTenantId, postgresType); + + databaseRegistryDbaasRepository.saveAnyTypeLogDb(registry1); + + BackupDatabase backupDatabase = getBackupDatabase(dbName1, List.of(getClassifier(namespace1, microserviceName1, tenantId)), false, BackupTaskStatus.COMPLETED, null); + BackupDatabase backupDatabase2 = getBackupDatabase(dbName2, List.of(getClassifier(namespace2, microserviceName2, null)), false, BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup = getLogicalBackup(logicalBackupName1, adapterId1, postgresType, List.of(backupDatabase), BackupTaskStatus.COMPLETED, null); + LogicalBackup logicalBackup2 = getLogicalBackup(logicalBackupName2, adapterId2, postgresType, List.of(backupDatabase2), BackupTaskStatus.COMPLETED, null); + Backup backup = getBackup(backupName, ExternalDatabaseStrategy.INCLUDE, getFilterCriteriaEntity(List.of(namespace1, namespace2)), List.of(logicalBackup, logicalBackup2), List.of(), BackupStatus.COMPLETED, null); + backupRepository.save(backup); + + DbaasAdapter dbaasAdapter1 = Mockito.mock(DbaasAdapter.class); + DbaasAdapter dbaasAdapter2 = Mockito.mock(DbaasAdapter.class); + + when(physicalDatabasesService.getAdapterById(adapterId1)).thenReturn(dbaasAdapter1); + when(dbaasAdapter1.isBackupRestoreSupported()).thenReturn(true); + + when(physicalDatabasesService.getAdapterById(adapterId2)).thenReturn(dbaasAdapter2); + when(dbaasAdapter2.isBackupRestoreSupported()).thenReturn(true); + + // Mock logic of choosing adapter in new/current env + ExternalAdapterRegistrationEntry adapter1 = new ExternalAdapterRegistrationEntry(); + PhysicalDatabase physicalDatabase1 = new PhysicalDatabase(); + physicalDatabase1.setAdapter(adapter1); + physicalDatabase1.setType(postgresType); + physicalDatabase1.setPhysicalDatabaseIdentifier("postgres-dev"); + + when(physicalDatabasesService.getByAdapterId(adapterId1)).thenReturn(physicalDatabase1); + + ExternalAdapterRegistrationEntry adapter2 = new ExternalAdapterRegistrationEntry(); + adapter2.setAdapterId(adapterId2); + PhysicalDatabase physicalDatabase2 = new PhysicalDatabase(); + physicalDatabase2.setAdapter(adapter2); + physicalDatabase2.setType(postgresType); + physicalDatabase2.setPhysicalDatabaseIdentifier("postgres-dev"); + + when(balancingRulesService.applyBalancingRules(postgresType, namespace2, microserviceName2)) + .thenReturn(physicalDatabase2); + when(physicalDatabasesService.getByAdapterId(adapterId2)).thenReturn(physicalDatabase2); + + + // Response during the sync restore process + LogicalRestoreAdapterResponse response = LogicalRestoreAdapterResponse.builder() + .status(IN_PROGRESS_STATUS) + .restoreId(logicalRestoreName1) + .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() + .status(IN_PROGRESS_STATUS) + .previousDatabaseName(dbName1) + .databaseName(newName1) + .duration(1) + .build())) + .build(); + + LogicalRestoreAdapterResponse response2 = LogicalRestoreAdapterResponse.builder() + .status(IN_PROGRESS_STATUS) + .restoreId(logicalRestoreName2) + .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() + .status(IN_PROGRESS_STATUS) + .previousDatabaseName(dbName2) + .databaseName(newName2) + .duration(1) + .build())) + .build(); + + // Response to DryRun, RealRun + when(dbaasAdapter1.restoreV2(eq(logicalBackupName1), anyBoolean(), any())) + .thenReturn(response) + .thenReturn(response); + when(dbaasAdapter2.restoreV2(eq(logicalBackupName2), anyBoolean(), any())) + .thenReturn(response2) + .thenReturn(response2); + + // Response during the async restore process + LogicalRestoreAdapterResponse response3 = LogicalRestoreAdapterResponse.builder() + .status(COMPLETED_STATUS) + .restoreId(logicalRestoreName1) + .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() + .status(COMPLETED_STATUS) + .previousDatabaseName(dbName1) + .databaseName(newName1) + .duration(1) + .build())) + .build(); + + LogicalRestoreAdapterResponse response4 = LogicalRestoreAdapterResponse.builder() + .status(COMPLETED_STATUS) + .restoreId(logicalRestoreName2) + .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() + .status(COMPLETED_STATUS) + .previousDatabaseName(dbName2) + .databaseName(newName2) + .duration(1) + .build())) + .build(); + + when(dbaasAdapter1.trackRestoreV2(eq(logicalRestoreName1), any(), any())) + .thenReturn(response3); + when(dbaasAdapter2.trackRestoreV2(eq(logicalRestoreName2), any(), any())) + .thenReturn(response4); + + // Mocks to ensure user process + DbResource resource1 = new DbResource(); + resource1.setId(UUID.randomUUID()); + resource1.setKind("kind"); + resource1.setName("name"); + EnsuredUser user1 = new EnsuredUser(); + user1.setConnectionProperties(Map.of( + "key", "value" + )); + user1.setResources(List.of(resource1)); + + when(dbaasAdapter1.ensureUser("username", null, newName1, "admin")).thenReturn(user1); + + DbResource resource2 = new DbResource(); + resource2.setId(UUID.randomUUID()); + resource2.setKind("kind"); + resource2.setName("name"); + EnsuredUser user2 = new EnsuredUser(); + user2.setConnectionProperties(Map.of( + "key", "value" + )); + user2.setResources(List.of(resource2)); + + when(dbaasAdapter2.ensureUser("username", null, newName2, "admin")).thenReturn(user2); + + Map namespaceMap = Map.of(namespace1, mappedNamespace); + Map tenantMap = Map.of(tenantId, mappedTenantId); + + RestoreResponse restoreResponse = dbBackupV2Service.restore(backupName, + getRestoreRequest(restoreName, List.of(namespace1, namespace2), ExternalDatabaseStrategy.INCLUDE, namespaceMap, tenantMap), + false + ); + dbBackupV2Service.checkRestoresAsync(); + + List databaseRegistries = databaseRegistryDbaasRepository.findAllDatabaseRegistersAnyLogType(); + assertEquals(5, databaseRegistries.size()); + + DatabaseRegistry replacedRegistry = databaseRegistries.stream() + .filter(r -> !r.getClassifier().containsKey(MARKED_FOR_DROP)) + .filter(r -> mappedNamespace.equals(r.getClassifier().get(NAMESPACE))) + .findAny().orElse(null); + assertNotNull(replacedRegistry); + assertEquals(adapterId1, replacedRegistry.getAdapterId()); + + DatabaseRegistry transientRegistry = databaseRegistries.stream() + .filter(r -> !r.getClassifier().containsKey(MARKED_FOR_DROP)) + .filter(r -> anotherNamespace.equals(r.getClassifier().get(NAMESPACE))) + .findAny().orElse(null); + assertNotNull(transientRegistry); + assertEquals(adapterId1, transientRegistry.getAdapterId()); + + DatabaseRegistry newRegistry = databaseRegistries.stream() + .filter(r -> namespace2.equals(r.getClassifier().get(NAMESPACE))) + .findAny().orElse(null); + assertNotNull(newRegistry); + assertEquals(adapterId2, newRegistry.getAdapterId()); + } + @Test void restore_dryRun() { String restoreName = "restoreName"; @@ -1776,8 +1964,8 @@ void retryRestore() { backupRepository.save(backup); Classifier classifierWrapper1 = getClassifier(ClassifierType.NEW, mappedNamespace, microserviceName1, null, namespace, null); - Classifier classifierWrapper2 = getClassifier(ClassifierType.NEW, null, microserviceName2, null, namespace, null); - Classifier classifierWrapper3 = getClassifier(ClassifierType.NEW, null, microserviceName3, null, namespace, null); + Classifier classifierWrapper2 = getClassifier(ClassifierType.NEW, namespace, microserviceName2, null, namespace, null); + Classifier classifierWrapper3 = getClassifier(ClassifierType.NEW, namespace, microserviceName3, null, namespace, null); RestoreExternalDatabase restoreExternalDatabase = getRestoreExternalDb(externalName, postgresType, List.of(classifierWrapper3)); RestoreDatabase restoreDatabase1 = getRestoreDatabase(backupDatabase1, newName, List.of(classifierWrapper1), Map.of(), null, RestoreTaskStatus.FAILED, 1, "Internal Server Error"); RestoreDatabase restoreDatabase2 = getRestoreDatabase(backupDatabase2, newName2, List.of(classifierWrapper2), Map.of(), null, RestoreTaskStatus.COMPLETED, 1, null); @@ -1932,16 +2120,114 @@ void updateAndValidateClassifier() { mapping.setNamespaces(Map.of(namespace, namespaceMapped)); Set> uniqueClassifiers = new HashSet<>(); - IllegalResourceStateException ex = assertThrows( - IllegalResourceStateException.class, - () -> classifiers.forEach(c -> - dbBackupV2Service.updateAndValidateClassifier(c, mapping, uniqueClassifiers) - ) - ); + } + + @Test + void updateClassifier_withoutMapping() { + SortedMap oldClassifier = new TreeMap<>(); + oldClassifier.put(NAMESPACE, "namespace"); + oldClassifier.put(MICROSERVICE_NAME, "microserviceName"); + oldClassifier.put(TENANT_ID, "tenant"); + oldClassifier.put(SCOPE, "tenant"); + + Classifier classifier = new Classifier(); + classifier.setClassifierBeforeMapper(oldClassifier); + + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), null); + assertEquals(1, updatedClassifiers.size()); + + Classifier updatedClassifier = updatedClassifiers.getFirst(); + assertEquals(updatedClassifier.getClassifier(), updatedClassifier.getClassifierBeforeMapper()); + assertEquals(updatedClassifier.getClassifierBeforeMapper(), oldClassifier); + } + + @Test + void updateClassifier_tenantMapping() { + String tenant = "tenant"; + String mappedTenant = "mappedTenant"; + + SortedMap oldClassifier = new TreeMap<>(); + oldClassifier.put(NAMESPACE, "namespace"); + oldClassifier.put(MICROSERVICE_NAME, "microserviceName"); + oldClassifier.put(TENANT_ID, tenant); + oldClassifier.put(SCOPE, tenant); + + Classifier classifier = new Classifier(); + classifier.setClassifierBeforeMapper(oldClassifier); + + Mapping mapping = new Mapping(); + mapping.setTenants(Map.of( + tenant, mappedTenant + )); + + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); + assertEquals(1, updatedClassifiers.size()); + Classifier updatedClassifier = updatedClassifiers.getFirst(); + SortedMap mappedClassifier = updatedClassifier.getClassifier(); + + assertNotNull(mappedClassifier); + assertEquals(updatedClassifier.getClassifierBeforeMapper(), oldClassifier); + + assertEquals(oldClassifier.get(NAMESPACE), mappedClassifier.get(NAMESPACE)); + assertEquals(oldClassifier.get(MICROSERVICE_NAME), mappedClassifier.get(MICROSERVICE_NAME)); + assertEquals(oldClassifier.get(SCOPE), mappedClassifier.get(SCOPE)); + assertEquals(mappedTenant, mappedClassifier.get(TENANT_ID)); + } + + @Test + void updateClassifier_nullTenantMapping() { + String tenant = "tenant"; + String mappedTenant = "mappedTenant"; + + SortedMap oldClassifier = new TreeMap<>(); + oldClassifier.put(NAMESPACE, "namespace"); + oldClassifier.put(MICROSERVICE_NAME, "microserviceName"); + oldClassifier.put(SCOPE, "service"); + + Classifier classifier = new Classifier(); + classifier.setClassifierBeforeMapper(oldClassifier); + + Mapping mapping = new Mapping(); + mapping.setTenants(Map.of( + tenant, mappedTenant + )); + + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); + assertEquals(1, updatedClassifiers.size()); + + Classifier updatedClassifier = updatedClassifiers.getFirst(); + assertEquals(updatedClassifier.getClassifier(), updatedClassifier.getClassifierBeforeMapper()); + assertEquals(updatedClassifier.getClassifierBeforeMapper(), oldClassifier); + } + + @Test + void updateClassifier_namespaceMapping() { + String namespace = "namespace"; + String mappedNamespace = "mappedNamespace"; + + SortedMap oldClassifier = new TreeMap<>(); + oldClassifier.put(NAMESPACE, namespace); + oldClassifier.put(MICROSERVICE_NAME, "microserviceName"); + oldClassifier.put(SCOPE, "service"); + + Classifier classifier = new Classifier(); + classifier.setClassifierBeforeMapper(oldClassifier); + + Mapping mapping = new Mapping(); + mapping.setNamespaces(Map.of( + namespace, mappedNamespace + )); + + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); + assertEquals(1, updatedClassifiers.size()); + + SortedMap mappedClassifier = updatedClassifiers.getFirst().getClassifier(); + + assertEquals(updatedClassifiers.getFirst().getClassifierBeforeMapper(), oldClassifier); - assertTrue(ex.getMessage().contains("Duplicate classifier detected after mapping: " + - "classifier='{microserviceName=test1, namespace=test1-namespace-mapped, scope=service}'. " + - "Ensure all classifiers remain unique after mapping")); + assertEquals(oldClassifier.get(MICROSERVICE_NAME), mappedClassifier.get(MICROSERVICE_NAME)); + assertEquals(oldClassifier.get(SCOPE), mappedClassifier.get(SCOPE)); + assertEquals(mappedNamespace, mappedClassifier.get(NAMESPACE)); } private static @NotNull List getClassifiers(String namespace, String namespaceMapped) { @@ -3291,7 +3577,7 @@ private Classifier getClassifier(ClassifierType classifierType, String namespace Classifier classifierWrapper = new Classifier(); classifierWrapper.setType(classifierType); - if(namespace != null && !namespace.isBlank()) { + if (namespace != null && !namespace.isBlank()) { SortedMap classifier = new TreeMap<>(); classifier.put("namespace", namespace); classifier.put("microserviceName", microserviceName); From b3fbe74d52e6f4ffad2afefc24e5e8ee1af04300 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 29 Jan 2026 17:19:40 +0500 Subject: [PATCH 28/46] fix: remove NOT_STARTED status from aggregate method and fix mapping --- .../com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java | 5 ++--- .../cloud/dbaas/repositories/pg/jpa/BackupRepository.java | 2 +- .../cloud/dbaas/repositories/pg/jpa/RestoreRepository.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java index a01efa79..7e03a3f1 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java @@ -78,7 +78,7 @@ default RestoreTaskStatus toRestoreTaskStatus(String status) { return mapStatus(status, RestoreTaskStatus::valueOf); } - private static , R extends Enum> R mapStatus( + private static > R mapStatus( String status, Function resultStatusGetter) { if (status == null) { @@ -88,8 +88,7 @@ private static , R extends Enum> R mapStatus( } return switch (status) { - case "notStarted" -> resultStatusGetter.apply("NOT_STARTED"); - case "inProgress" -> resultStatusGetter.apply("IN_PROGRESS"); + case "notStarted", "inProgress" -> resultStatusGetter.apply("IN_PROGRESS"); case "completed" -> resultStatusGetter.apply("COMPLETED"); case "failed" -> resultStatusGetter.apply("FAILED"); default -> throw new UnprocessableEntityException( diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java index 8abb52fc..43396a08 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java @@ -20,6 +20,6 @@ public Backup save(Backup backup) { } public List findBackupsToAggregate() { - return list("status in ?1", List.of(BackupStatus.NOT_STARTED, BackupStatus.IN_PROGRESS)); + return list("status in ?1", List.of(BackupStatus.IN_PROGRESS)); } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java index 34034971..0822f451 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java @@ -20,7 +20,7 @@ public Restore save(Restore restore) { } public List findRestoresToAggregate() { - return list("status in ?1", List.of(RestoreStatus.NOT_STARTED, RestoreStatus.IN_PROGRESS)); + return list("status in ?1", List.of(RestoreStatus.IN_PROGRESS)); } public long countNotCompletedRestores() { From b56a238cef1421a78067907663e88fe21be05e6c Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 29 Jan 2026 17:21:12 +0500 Subject: [PATCH 29/46] style: remove redundant required attribute --- .../cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index 8bf429cf..836e2e8d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -72,7 +72,7 @@ public DatabaseBackupV2Controller(DbBackupV2Service dbBackupV2Service) { }) @Path("/backup") @POST - public Response initiateBackup(@RequestBody(description = "Backup request", required = true) @Valid BackupRequest backupRequest, + public Response initiateBackup(@RequestBody(description = "Backup request") @Valid BackupRequest backupRequest, @QueryParam("dryRun") @DefaultValue("false") boolean dryRun) { BackupResponse response = dbBackupV2Service.backup(backupRequest, dryRun); BackupStatus status = response.getStatus(); @@ -202,7 +202,7 @@ public Response uploadMetadata( "sha-256=nOJRJg..." })) @HeaderParam("Digest") @NotNull String digestHeader, - @RequestBody(description = "Backup metadata", required = true) @Valid BackupResponse backupResponse + @RequestBody(description = "Backup metadata") @Valid BackupResponse backupResponse ) { String calculatedDigest = DigestUtil.calculateDigest(backupResponse); if (!calculatedDigest.equals(digestHeader)) From 98874aefd23aa0880fd76c77c87300f68318766c Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 30 Jan 2026 13:54:48 +0500 Subject: [PATCH 30/46] style: fix OpenApi spec --- .../v3/DatabaseBackupV2Controller.java | 54 +++++++++++-------- .../dto/backupV2/BackupDatabaseResponse.java | 4 +- .../dbaas/dto/backupV2/BackupRequest.java | 4 +- .../dbaas/dto/backupV2/BackupResponse.java | 4 +- .../dbaas/dto/backupV2/FilterCriteria.java | 6 +-- .../dto/backupV2/RestoreDatabaseResponse.java | 4 +- .../RestoreExternalDatabaseResponse.java | 8 ++- .../dbaas/dto/backupV2/RestoreRequest.java | 2 +- .../dbaas/dto/backupV2/RestoreResponse.java | 4 +- .../cloud/dbaas/utils/DigestUtil.java | 3 +- 10 files changed, 53 insertions(+), 40 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index 836e2e8d..37a856af 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -49,7 +49,7 @@ public DatabaseBackupV2Controller(DbBackupV2Service dbBackupV2Service) { @Operation(summary = "Initiate database backup", description = "Starts an asynchronous backup operation for the specified databases." - + " Returns immediately with a backup identifier that can be used to track progress.") + + " Returns immediately with a backup name that can be used to track progress.") @APIResponses({ @APIResponse(responseCode = "200", description = "Backup operation completed successfully", content = @Content(schema = @Schema(implementation = BackupResponse.class))), @@ -66,8 +66,6 @@ public DatabaseBackupV2Controller(DbBackupV2Service dbBackupV2Service) { @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", - content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), - @APIResponse(responseCode = "501", description = "The server does not support the functionality required to fulfill the request", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))) }) @Path("/backup") @@ -85,6 +83,8 @@ public Response initiateBackup(@RequestBody(description = "Backup request") @Val @APIResponses({ @APIResponse(responseCode = "200", description = "Backup details retrieved successfully", content = @Content(schema = @Schema(implementation = BackupResponse.class))), + @APIResponse(responseCode = "400", description = "The request was invalid or cannot be served", + content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "401", description = "Authentication is required and has failed or has not been provided"), @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), @APIResponse(responseCode = "404", description = "The requested resource could not be found", @@ -94,7 +94,9 @@ public Response initiateBackup(@RequestBody(description = "Backup request") @Val }) @Path("/backup/{backupName}") @GET - public Response getBackup(@Parameter(description = "Unique identifier of the backup", required = true) @PathParam("backupName") String backupName) { + public Response getBackup(@Parameter(description = "Unique name of the backup", required = true) + @PathParam("backupName") + @NotBlank String backupName) { return Response.ok(dbBackupV2Service.getBackup(backupName)).build(); } @@ -102,17 +104,19 @@ public Response getBackup(@Parameter(description = "Unique identifier of the bac @APIResponses({ @APIResponse(responseCode = "202", description = "Backup delete initialized successfully"), @APIResponse(responseCode = "204", description = "Backup deleted successfully"), + @APIResponse(responseCode = "400", description = "The request was invalid or cannot be served", + content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "401", description = "Authentication is required and has failed or has not been provided"), @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), - @APIResponse(responseCode = "404", description = "The requested resource could not be found", + @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))) }) @Path("/backup/{backupName}") @DELETE - public Response deleteBackup(@Parameter(description = "Unique identifier of the backup", required = true) - @PathParam("backupName") String backupName, + public Response deleteBackup(@Parameter(description = "Unique name of the backup", required = true) + @PathParam("backupName") @NotBlank String backupName, @QueryParam("force") @DefaultValue("false") boolean force) { dbBackupV2Service.deleteBackup(backupName, force); if (force) @@ -124,6 +128,8 @@ public Response deleteBackup(@Parameter(description = "Unique identifier of the @APIResponses({ @APIResponse(responseCode = "200", description = "Backup status retrieved successfully", content = @Content(schema = @Schema(implementation = BackupStatusResponse.class))), + @APIResponse(responseCode = "400", description = "The request was invalid or cannot be served", + content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "401", description = "Authentication is required and has failed or has not been provided"), @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), @APIResponse(responseCode = "404", description = "The requested resource could not be found", @@ -133,7 +139,7 @@ public Response deleteBackup(@Parameter(description = "Unique identifier of the }) @Path("/backup/{backupName}/status") @GET - public Response getBackupStatus(@Parameter(description = "Unique identifier of the backup", required = true) + public Response getBackupStatus(@Parameter(description = "Unique name of the backup", required = true) @PathParam("backupName") @NotBlank String backupName) { return Response.ok(dbBackupV2Service.getCurrentStatus(backupName)).build(); @@ -155,6 +161,8 @@ public Response getBackupStatus(@Parameter(description = "Unique identifier of t }, content = @Content(schema = @Schema(implementation = BackupResponse.class)) ), + @APIResponse(responseCode = "400", description = "The request was invalid or cannot be served", + content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "401", description = "Authentication is required and has failed or has not been provided"), @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), @APIResponse(responseCode = "404", description = "The requested resource could not be found", @@ -166,7 +174,7 @@ public Response getBackupStatus(@Parameter(description = "Unique identifier of t }) @Path("/backup/{backupName}/metadata") @GET - public Response getBackupMetadata(@Parameter(description = "Unique identifier of the backup", required = true) + public Response getBackupMetadata(@Parameter(description = "Unique name of the backup", required = true) @PathParam("backupName") @NotBlank String backupName) { BackupResponse response = dbBackupV2Service.getBackupMetadata(backupName); @@ -176,7 +184,7 @@ public Response getBackupMetadata(@Parameter(description = "Unique identifier of .build(); } - @Operation(summary = "Upload backup metadata", description = "Metadata upload done") + @Operation(summary = "Upload backup metadata", description = "Upload backup metadata") @APIResponses({ @APIResponse(responseCode = "200", description = "Backup metadata uploaded successfully"), @APIResponse(responseCode = "400", description = "The request was invalid or cannot be served", @@ -193,13 +201,13 @@ public Response getBackupMetadata(@Parameter(description = "Unique identifier of public Response uploadMetadata( @Parameter( name = "Digest", - description = "Digest header in format: sha-256=", + description = "Digest header in format: SHA-256=", required = true, in = ParameterIn.HEADER, schema = @Schema( type = SchemaType.STRING, examples = { - "sha-256=nOJRJg..." + "SHA-256=nOJRJg..." })) @HeaderParam("Digest") @NotNull String digestHeader, @RequestBody(description = "Backup metadata") @Valid BackupResponse backupResponse @@ -215,7 +223,7 @@ public Response uploadMetadata( } @Operation(summary = "Restore from backup", description = "Initiate a database restore operation from an existing backup." + - "This operation is asynchronous and returns immediately with a restore identifier that can be used to track progress." + + "This operation is asynchronous and returns immediately with a restore name that can be used to track progress." + "Operation is not idempotent") @APIResponses({ @APIResponse(responseCode = "200", description = "Restore operation completed successfully", @@ -233,13 +241,11 @@ public Response uploadMetadata( @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", - content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), - @APIResponse(responseCode = "501", description = "The server does not support the functionality required to fulfill the request", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))) }) @Path("/backup/{backupName}/restore") @POST - public Response restoreBackup(@Parameter(description = "Unique identifier of the backup", required = true) + public Response restoreBackup(@Parameter(description = "Unique name of the backup", required = true) @PathParam("backupName") @NotBlank String backupName, @RequestBody(description = "Restore request") @Valid RestoreRequest restoreRequest, @@ -255,6 +261,8 @@ public Response restoreBackup(@Parameter(description = "Unique identifier of the @APIResponses({ @APIResponse(responseCode = "200", description = "Restore details retrieved successfully", content = @Content(schema = @Schema(implementation = RestoreResponse.class))), + @APIResponse(responseCode = "400", description = "The request was invalid or cannot be served", + content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "401", description = "Authentication is required and has failed or has not been provided"), @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), @APIResponse(responseCode = "404", description = "The requested resource could not be found", @@ -264,7 +272,7 @@ public Response restoreBackup(@Parameter(description = "Unique identifier of the }) @Path("/restore/{restoreName}") @GET - public Response getRestore(@Parameter(description = "Unique identifier of the restore operation", required = true) + public Response getRestore(@Parameter(description = "Unique name of the restore operation", required = true) @PathParam("restoreName") @NotBlank String restoreName) { return Response.ok(dbBackupV2Service.getRestore(restoreName)).build(); @@ -275,14 +283,16 @@ public Response getRestore(@Parameter(description = "Unique identifier of the re @APIResponse(responseCode = "204", description = "Restore operation deleted successfully"), @APIResponse(responseCode = "401", description = "Authentication is required and has failed or has not been provided"), @APIResponse(responseCode = "403", description = "The request was valid, but the server is refusing action"), - @APIResponse(responseCode = "404", description = "The requested resource could not be found", + @APIResponse(responseCode = "422", description = "The request was accepted, but the server couldn't process due to incompatible resource", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))), @APIResponse(responseCode = "500", description = "An unexpected error occurred on the server", content = @Content(schema = @Schema(implementation = TmfErrorResponse.class))) }) @Path("/restore/{restoreName}") @DELETE - public Response deleteRestore(@Parameter(description = "Unique identifier of the restore operation", required = true) @PathParam("restoreName") String restoreName) { + public Response deleteRestore(@Parameter(description = "Unique name of the restore operation", required = true) + @PathParam("restoreName") + @NotBlank String restoreName) { dbBackupV2Service.deleteRestore(restoreName); return Response.noContent().build(); } @@ -300,7 +310,7 @@ public Response deleteRestore(@Parameter(description = "Unique identifier of the }) @Path("/restore/{restoreName}/status") @GET - public Response getRestoreStatus(@Parameter(description = "Unique identifier of the restore operation", required = true) + public Response getRestoreStatus(@Parameter(description = "Unique name of the restore operation", required = true) @PathParam("restoreName") @NotBlank String restoreName) { return Response.ok(dbBackupV2Service.getRestoreStatus(restoreName)).build(); @@ -323,9 +333,9 @@ public Response getRestoreStatus(@Parameter(description = "Unique identifier of }) @Path("/restore/{restoreName}/retry") @POST - public Response retryRestore(@Parameter(description = "Unique identifier of the restore operation", required = true) + public Response retryRestore(@Parameter(description = "Unique name of the restore operation", required = true) @PathParam("restoreName") - String restoreName) { + @NotBlank String restoreName) { return Response.accepted(dbBackupV2Service.retryRestore(restoreName)).build(); } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java index 77465a70..9e965b56 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java @@ -31,12 +31,12 @@ public class BackupDatabaseResponse { private List> classifiers; @Schema( description = "Database settings as a key-value map", - examples = "{\"key\":value, \"key\":value}" + examples = "{\"key\": \"value\", \"key\": \"value\"}" ) private Map settings; @Schema( description = "List of database users", - examples = "[{\"name\":\"username\",\"role\":\"admin\"}" + examples = "[{\"name\":\"username\",\"role\":\"admin\"}]" ) private List users; @Schema( diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java index ac7527af..506e29d4 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java @@ -17,7 +17,7 @@ public class BackupRequest { @NotBlank @Schema( - description = "Unique identifier of the backup", + description = "Unique name of the backup", examples = { "before-prod-update-20251013T1345-G5s8" }, @@ -59,7 +59,7 @@ public class BackupRequest { private ExternalDatabaseStrategy externalDatabaseStrategy = ExternalDatabaseStrategy.FAIL; @NotNull @Schema( - description = "Whether non-backupable databases should be ignored during backup", + description = "Whether non‑backupable databases were ignored during backup", examples = { "false" }, diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java index ffd52995..980daeb5 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupResponse.java @@ -21,7 +21,7 @@ public class BackupResponse { @Schema( - description = "Unique identifier of the backup", + description = "Unique name of the backup", examples = { "before-prod-update-20251013T1345-G5s8" }, @@ -57,7 +57,7 @@ public class BackupResponse { @NotNull private ExternalDatabaseStrategy externalDatabaseStrategy; @Schema( - description = "Whether external databases were skipped during the backup", + description = "Whether non‑backupable databases were ignored during backup", examples = { "false" } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java index 70144f08..72c03edb 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java @@ -16,12 +16,10 @@ @Schema(description = "Group of filters for backup and restore operations. Filters are applied in the following order:\n" + "\n" + "1. `filter`: Apply the filter to the databases.\n" + - "2. `include`: Include databases that match any of the filters in the list.\n" + - "3. `exclude`: Exclude databases that match any of the filters in the list.") + "2. `exclude`: Exclude databases that match any of the filters in the list.") public class FilterCriteria { @Schema( - description = "Apply the filter to the remaining databases", - required = true + description = "Apply the filter to the remaining databases" ) @NotNull(groups = {BackupGroup.class}) @Size(min = 1, groups = {BackupGroup.class}) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java index d3f0669f..ba213389 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java @@ -34,7 +34,7 @@ public class RestoreDatabaseResponse { private List users; @Schema( description = "Database settings as a key-value map", - examples = "{\"key\":value, \"key\":value}" + examples = "{\"key\": \"value\", \"key\": \"value\"}" ) private Map settings; @Schema( @@ -58,7 +58,7 @@ public class RestoreDatabaseResponse { required = true ) private String path; - @Schema(description = "Error message if the backup failed", examples = "Restore Not Found") + @Schema(description = "Error message if the restore failed", examples = "Restore Not Found") private String errorMessage; @Schema(description = "Timestamp when the restore was created", examples = "2025-11-13T12:34:56Z") private Instant creationTime; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index 112480be..f95860db 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -1,7 +1,10 @@ package com.netcracker.cloud.dbaas.dto.backupV2; +import com.netcracker.cloud.core.error.rest.tmf.TmfErrorResponse; import lombok.Data; import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.Schema; import java.util.List; @@ -15,8 +18,9 @@ public class RestoreExternalDatabaseResponse { @Schema(description = "Type of the database", examples = "postgresql") private String type; @Schema( - description = "List of database classifiers. Each classifier is a sorted map of attributes.", - examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]" + description = "List of classifier objects describing database attributes.", + implementation = ClassifierResponse.class, + type = SchemaType.ARRAY ) private List classifiers; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java index 1bccd4a1..4b023f48 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java @@ -16,7 +16,7 @@ @Schema(description = "Request to restore a database from a backup") public class RestoreRequest { @Schema( - description = "Unique identifier of the restore", + description = "Unique name of the restore", required = true, examples = { "restore-before-prod-update-20251203T1020-4t6S" diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java index 376697e3..63edc940 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java @@ -20,7 +20,7 @@ @Schema(description = "Response containing the restore operation details") public class RestoreResponse { @Schema( - description = "Unique identifier of the restore", + description = "Unique name of the restore", examples = { "restore-before-prod-update-20251203T1020-4t6S" }, @@ -29,7 +29,7 @@ public class RestoreResponse { @NotBlank private String restoreName; @Schema( - description = "Unique identifier of the backup", + description = "Unique name of the backup", examples = { "before-prod-update-20251013T1345-G5s8" } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/DigestUtil.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/DigestUtil.java index 3f35f5d7..3ae5968e 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/DigestUtil.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/utils/DigestUtil.java @@ -9,6 +9,7 @@ import com.netcracker.cloud.dbaas.exceptions.DigestCalculationException; import lombok.extern.slf4j.Slf4j; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; @@ -29,7 +30,7 @@ public static String calculateDigest(Object obj) { String json = OBJECT_MAPPER.writeValueAsString(obj); MessageDigest digest = MessageDigest.getInstance(ALGORITHM); - byte[] hash = digest.digest(json.getBytes()); + byte[] hash = digest.digest(json.getBytes(StandardCharsets.UTF_8)); String base64Hash = Base64.getEncoder().encodeToString(hash); return ALGORITHM + "=" + base64Hash; From 126050692a64339816bd278249f988de8218c51f Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 30 Jan 2026 14:06:35 +0500 Subject: [PATCH 31/46] feat: add new flyway migration that mark all status fields in entities as not null --- .../V1.034__Backup_Restore_Not_Null_Statuses.sql | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Backup_Restore_Not_Null_Statuses.sql diff --git a/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Backup_Restore_Not_Null_Statuses.sql b/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Backup_Restore_Not_Null_Statuses.sql new file mode 100644 index 00000000..c360a4ec --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Backup_Restore_Not_Null_Statuses.sql @@ -0,0 +1,13 @@ +UPDATE backup SET status = 'FAILED' WHERE status IS NULL; +UPDATE backup_logical SET status = 'FAILED' WHERE status IS NULL; +UPDATE backup_database SET status = 'FAILED' WHERE status IS NULL; +UPDATE restore SET status = 'FAILED' WHERE status IS NULL; +UPDATE restore_logical SET status = 'FAILED' WHERE status IS NULL; +UPDATE restore_database SET status = 'FAILED' WHERE status IS NULL; + +ALTER TABLE backup ALTER COLUMN status SET NOT NULL; +ALTER TABLE backup_logical ALTER COLUMN status SET NOT NULL; +ALTER TABLE backup_database ALTER COLUMN status SET NOT NULL; +ALTER TABLE restore ALTER COLUMN status SET NOT NULL; +ALTER TABLE restore_logical ALTER COLUMN status SET NOT NULL; +ALTER TABLE restore_database ALTER COLUMN status SET NOT NULL; From 09d947b1de4287f169b34cf4aca0116a878b6ebe Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 30 Jan 2026 14:07:07 +0500 Subject: [PATCH 32/46] fix: add default status to logical backup --- .../cloud/dbaas/entity/pg/backupV2/LogicalRestore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java index 7aeab9f4..495fc028 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java @@ -39,7 +39,7 @@ public class LogicalRestore { @Enumerated(EnumType.STRING) @Column(name = "status") - private RestoreTaskStatus status; + private RestoreTaskStatus status = RestoreTaskStatus.NOT_STARTED; @Column(name = "error_message") private String errorMessage; From d4a010fdc62546b68323bdbf7de72c230060faa8 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 30 Jan 2026 14:08:39 +0500 Subject: [PATCH 33/46] fix: take out try catch block from transactional method --- .../dbaas/service/DbBackupV2Service.java | 245 ++++++++++-------- 1 file changed, 133 insertions(+), 112 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index d848347b..c4f0b431 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -57,6 +57,9 @@ public class DbBackupV2Service { private static final String TRACK_RESTORE_OPERATION = "trackRestoreV2"; private static final String RESTORE = "restore"; private static final String DATABASE_NAME = "databaseName"; + private static final String LOGICAL_BACKUP = "logicalBackup"; + private static final String LOGICAL_RESTORE = "logicalRestore"; + private static final String RESTORE_DATABASE = "restoreDatabase"; private static final Integer LOCK_AT_MOST = 2; private static final Integer LOCK_AT_LEAST = 0; @@ -193,9 +196,9 @@ private LogicalBackup createLogicalBackup(String adapterId, Map Map.of(DATABASE_NAME, db.getName())) .toList(); - RetryPolicy retryPolicy = buildRetryPolicy(logicalBackup.getLogicalBackupName(), BACKUP_OPERATION); + RetryPolicy retryPolicy = buildRetryPolicy(BACKUP_OPERATION, LOGICAL_BACKUP, logicalBackup.getId().toString(), adapterId); try { return Failsafe.with(retryPolicy).get(() -> { @@ -343,7 +346,7 @@ private void fetchAndUpdateStatuses(Backup backup) { private CompletableFuture trackLogicalBackupAsync(LogicalBackup logicalBackup) { RetryPolicy retryPolicy = - buildRetryPolicy(logicalBackup.getLogicalBackupName(), TRACK_BACKUP_OPERATION); + buildRetryPolicy(TRACK_BACKUP_OPERATION, LOGICAL_BACKUP, logicalBackup.getId().toString(), logicalBackup.getAdapterId()); return CompletableFuture.supplyAsync( asyncOperations.wrapWithContext(() -> Failsafe.with(retryPolicy) @@ -353,6 +356,7 @@ private CompletableFuture trackLogicalBackupAsync(LogicalBackup logicalBac .thenAccept(response -> refreshLogicalBackupState(logicalBackup, response)) .exceptionally(throwable -> { logicalBackup.setErrorMessage(extractErrorMessage(throwable)); + logicalBackup.setStatus(BackupTaskStatus.FAILED); return null; }); } @@ -366,9 +370,9 @@ private LogicalBackupAdapterResponse executeTrackBackup(LogicalBackup logicalBac ); if (response == null) { - log.error("Empty response from {} for {}", TRACK_BACKUP_OPERATION, logicalBackup.getLogicalBackupName()); + log.error("Empty response from {} for logicalBackup={}", TRACK_BACKUP_OPERATION, logicalBackup.getId()); throw new BackupExecutionException( - String.format("Empty response from %s for %s", TRACK_BACKUP_OPERATION, logicalBackup.getLogicalBackupName()), + String.format("Empty response from %s for logicalBackup=%s", TRACK_BACKUP_OPERATION, logicalBackup.getId()), new Throwable() ); } @@ -556,7 +560,7 @@ public void uploadBackupMetadata(BackupResponse backupResponse) { } } else { throw new IllegalResourceStateException( - String.format("can't restore %s backup that not imported", BackupStatus.DELETED), + String.format("can't restore a %s backup that is not imported", BackupStatus.DELETED), Source.builder().build() ); } @@ -564,7 +568,7 @@ public void uploadBackupMetadata(BackupResponse backupResponse) { // if backup status not DELETED throw new IllegalResourceStateException( - String.format("backup already exists and is not %s status", BackupStatus.DELETED), + String.format("backup already exists and is not in %s status", BackupStatus.DELETED), Source.builder().build() ); } @@ -617,7 +621,7 @@ private CompletableFuture deleteLogicalBackupAsync( ) { String adapterId = logicalBackup.getAdapterId(); RetryPolicy retryPolicy = - buildRetryPolicy(logicalBackup.getLogicalBackupName(), DELETE_BACKUP_OPERATION); + buildRetryPolicy(DELETE_BACKUP_OPERATION, LOGICAL_BACKUP, logicalBackup.getId().toString(), adapterId); log.info("Backup with adapterId {} has {} databases to delete", adapterId, logicalBackup.getBackupDatabases().size()); @@ -665,7 +669,7 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, log.info("Start restore {} for backup {}", restoreName, backupName); Backup backup = getBackupOrThrowException(backupName); - checkBackupStatusForRestore(backupName, backup.getStatus()); + checkBackupStatusForRestore(restoreName, backup.getStatus()); Restore restore = restoreLockWrapper(() -> { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); @@ -753,14 +757,15 @@ protected Restore initializeFullRestoreStructure( List mappedBackupDatabases = applyMappingToBackupDatabases(filteredBackupDatabases, restoreRequest.getMapping()); + checkForCollision(mappedExternalDbs, mappedBackupDatabases); // Enrich classifiers - List enrichedBackupClassifiers = applySimilarDbClassifiers(mappedBackupDatabases); + List enrichedBackupClassifiers = enrichInternalDbClassifiers(mappedBackupDatabases); + List enrichedExternalDbs = enrichExternalDbClassifiers(mappedExternalDbs); // Group BackupDatabases by updated adapter and logicalBackupName Map> groupedByTypeAndAdapter = groupBackupDatabasesByLogicalBackupNameAndAdapter(enrichedBackupClassifiers); - checkForCollision(mappedExternalDbs, groupedByTypeAndAdapter.values().stream().flatMap(List::stream).toList()); log.info("Initializing restore structure: restoreName={}, backupName={}", restoreRequest.getRestoreName(), backup.getName()); @@ -790,19 +795,19 @@ protected Restore initializeFullRestoreStructure( restore.setBlobPath(restoreRequest.getBlobPath()); restore.setLogicalRestores(new ArrayList<>(logicalRestores)); restore.setExternalDatabaseStrategy(restoreRequest.getExternalDatabaseStrategy()); - restore.setExternalDatabases(mappedExternalDbs); + restore.setExternalDatabases(enrichedExternalDbs); restore.setMapping(mapper.toMappingEntity(restoreRequest.getMapping())); restore.setFilterCriteria(mapper.toFilterCriteriaEntity(restoreRequest.getFilterCriteria())); // set up relation logicalRestores.forEach(lr -> lr.setRestore(restore)); - mappedExternalDbs.forEach(db -> db.setRestore(restore)); + enrichedExternalDbs.forEach(db -> db.setRestore(restore)); int totalDatabases = logicalRestores.stream() .mapToInt(lr -> lr.getRestoreDatabases().size()) .sum(); log.info("Restore structure initialized: restoreName={}, logicalRestores={}, restoreDatabases={}, externalDatabases={}", - restore.getName(), logicalRestores.size(), totalDatabases, mappedExternalDbs.size()); + restore.getName(), logicalRestores.size(), totalDatabases, enrichedExternalDbs.size()); return restore; } @@ -836,7 +841,7 @@ protected List applyMapping(List classifiers, Mapping ma return mappedClassifiers; } - private List applySimilarDbClassifiers(List backupDatabases) { + private List enrichInternalDbClassifiers(List backupDatabases) { return backupDatabases.stream() .map(db -> { String type = db.backupDatabase().getLogicalBackup().getType(); @@ -845,6 +850,18 @@ private List applySimilarDbClassifiers(List enrichExternalDbClassifiers(List externalDatabases) { + List enrichedExternalDbs = new ArrayList<>(); + + for (RestoreExternalDatabase db : externalDatabases) { + List classifiers = findSimilarDbByClassifier(db.getClassifiers(), db.getType()).stream().toList(); + db.setClassifiers(classifiers); + enrichedExternalDbs.add(db); + } + + return enrichedExternalDbs; + } + private void checkForCollision(List externalDatabases, List backupDatabases) { Set> uniqueClassifiers = new HashSet<>(); List duplicateClassifiers = new ArrayList<>(); @@ -928,7 +945,7 @@ private void collectDuplicateClassifiers( String microserviceName = (String) classifier.get(MICROSERVICE_NAME); String type = db.getType(); return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, false)) - && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); + && filterCriteria.getExclude().stream().filter(exclude -> !isEmpty(exclude)).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); }) .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList(); @@ -953,8 +970,7 @@ private List executeMappingForExternalDb( for (RestoreExternalDatabase externalDatabase : externalDatabases) { List mappedClassifiers = applyMapping(externalDatabase.getClassifiers(), mapping); - List enrichedClassifiers = findSimilarDbByClassifier(mappedClassifiers, externalDatabase.getType()).stream().toList(); - externalDatabase.setClassifiers(enrichedClassifiers); + externalDatabase.setClassifiers(mappedClassifiers); updatedExternals.add(externalDatabase); } return updatedExternals; @@ -1056,7 +1072,7 @@ private Map.Entry mapToAdapterBackupKe protected void startRestore(Restore restore, boolean dryRun) { List logicalRestores = restore.getLogicalRestores(); - log.info("Starting requesting adapters to restore startup process: restore={}, dryRun={} logicalRestoreCount={}", + log.info("Starting requesting adapters to restore startup process: restore={}, dryRun={}, logicalRestoreCount={}", restore.getName(), dryRun, logicalRestores.size()); String storageName = restore.getStorageName(); String blobPath = restore.getBlobPath(); @@ -1079,14 +1095,14 @@ private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRes return CompletableFuture.supplyAsync( asyncOperations.wrapWithContext( () -> logicalRestore( - logicalRestore.getLogicalRestoreName(), logicalRestore, restoreDatabases, storageName, blobPath, dryRun ) - ) + ), + asyncOperations.getBackupPool() ) .thenAccept(response -> refreshLogicalRestoreState(logicalRestore, response)) @@ -1143,8 +1159,7 @@ private void refreshLogicalRestoreState(LogicalRestore logicalRestore, LogicalRe logicalRestore.getLogicalRestoreName(), logicalRestore.getStatus(), logicalRestore.getErrorMessage()); } - private LogicalRestoreAdapterResponse logicalRestore(String logicalRestoreName, - LogicalRestore logicalRestore, + private LogicalRestoreAdapterResponse logicalRestore(LogicalRestore logicalRestore, List restoreDatabases, String storageName, String blobPath, @@ -1156,7 +1171,7 @@ private LogicalRestoreAdapterResponse logicalRestore(String logicalRestoreName, .getLogicalBackupName(); List> databases = buildRestoreDatabases(restoreDatabases); - RetryPolicy retryPolicy = buildRetryPolicy(logicalRestoreName, RESTORE_OPERATION); + RetryPolicy retryPolicy = buildRetryPolicy(RESTORE_OPERATION, LOGICAL_RESTORE, logicalRestore.getId().toString(), logicalRestore.getAdapterId()); return Failsafe.with(retryPolicy) .get(() -> executeRestore(logicalRestore, logicalBackupName, storageName, blobPath, databases, dryRun)); @@ -1225,33 +1240,42 @@ protected void checkRestoresAsync() { restoresToAggregate.forEach(restore -> { trackAndAggregateRestore(restore); - if (RestoreStatus.COMPLETED != restore.getStatus() || !Objects.equals(restore.getTotal(), restore.getCompleted())) { + if (RestoreStatus.COMPLETED != restore.getStatus()) { restoreRepository.save(restore); return; } - Map> dbNameToEnsuredUsers = restore.getLogicalRestores().stream() - .flatMap(lr -> lr.getRestoreDatabases().stream() - .map(rd -> Map.entry( - rd.getName(), - ensureUsers(lr.getAdapterId(), rd.getName(), rd.getUsers()) - )) - ) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); + try { + Map> dbNameToEnsuredUsers = restore.getLogicalRestores().stream() + .flatMap(lr -> lr.getRestoreDatabases().stream() + .map(rd -> Map.entry( + rd.getName(), + ensureUsers(lr.getAdapterId(), rd.getId().toString(), rd.getName(), rd.getUsers()) + )) + ) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); - initializeLogicalDatabasesFromRestore(restore, dbNameToEnsuredUsers); - restoreRepository.save(restore); + initializeLogicalDatabasesFromRestore(restore, dbNameToEnsuredUsers); + restoreRepository.save(restore); + } catch (Exception e) { + log.error("Exception occurred during restore process", e); + restore.setStatus(RestoreStatus.FAILED); + restore.setErrorMessage(extractErrorMessage(e)); + restoreRepository.save(restore); + throw e; + } }); } protected List ensureUsers(String adapterId, + String dbId, String dbName, List users) { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(adapterId); - RetryPolicy retryPolicy = buildRetryPolicy(dbName, ENSURE_USER_OPERATION); + RetryPolicy retryPolicy = buildRetryPolicy(ENSURE_USER_OPERATION, RESTORE_DATABASE, dbId, adapterId); - log.info("Start ensure {} users for database=[{}] via adapter [{}]", - users.size(), dbName, adapterId); + log.info("Start ensuring {} users for database=[{}] via adapter [{}]", + users.size(), dbId, adapterId); return users.stream() .map(user -> { @@ -1259,14 +1283,14 @@ protected List ensureUsers(String adapterId, return Failsafe.with(retryPolicy) .get(() -> { EnsuredUser ensuredUser = adapter.ensureUser(user.getName(), null, dbName, user.getRole()); - log.info("User ensured for database=[{}], user information=[name={}, connectionProperties={}]", - dbName, ensuredUser.getName(), ensuredUser.getConnectionProperties()); + log.info("User ensured for database=[{}], user=[name:{}, connectionProperties:{}]", + dbId, ensuredUser.getName(), ensuredUser.getConnectionProperties()); return ensuredUser; }); } catch (Exception e) { - log.error("Failed to ensure user {} in database {}", user.getName(), dbName, e); + log.error("Failed to ensure user [{}] in database [{}]", user.getName(), dbId); throw new BackupExecutionException( - String.format("Failed to ensure user %s", user.getName()), e); + String.format("Failed to ensure user [%s], in database [%s]", user.getName(), dbId), e); } }) .toList(); @@ -1275,7 +1299,7 @@ protected List ensureUsers(String adapterId, protected void trackAndAggregateRestore(Restore restore) { if (restore.getAttemptCount() > retryCount) { - log.warn("The number of attempts of track restore {} exceeded {}", restore.getName(), retryCount); + log.warn("The number of attempts to track restore {} exceeded {}", restore.getName(), retryCount); restore.setStatus(RestoreStatus.FAILED); restore.setErrorMessage(String.format("The number of attempts exceeded %s", retryCount)); } else { @@ -1299,7 +1323,7 @@ private void fetchStatuses(Restore restore) { List> futures = notFinishedLogicalRestores.stream() .map(logicalRestore -> { - RetryPolicy retryPolicy = buildRetryPolicy(logicalRestore.getLogicalRestoreName(), TRACK_RESTORE_OPERATION); + RetryPolicy retryPolicy = buildRetryPolicy(TRACK_RESTORE_OPERATION, LOGICAL_RESTORE, logicalRestore.getId().toString(), logicalRestore.getAdapterId()); return CompletableFuture.supplyAsync( asyncOperations.wrapWithContext(() -> Failsafe.with(retryPolicy).get(() -> { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalRestore.getAdapterId()); @@ -1360,65 +1384,58 @@ private void aggregateRestoreStatus(Restore restore) { @Transactional protected void initializeLogicalDatabasesFromRestore(Restore restore, Map> dbNameToEnsuredUsers) { - log.info("Start creating logicalDatabases from restore {}", restore.getName()); - try { - // Creating LogicalDb based logicalRestores - restore.getLogicalRestores().forEach(logicalRestore -> { - log.info("Processing logicalRestore={}, type={}, adapterId={}", logicalRestore.getLogicalRestoreName(), logicalRestore.getType(), logicalRestore.getAdapterId()); - logicalRestore.getRestoreDatabases().forEach(restoreDatabase -> { - String type = logicalRestore.getType(); - log.info("Processing restoreDatabase={}", restoreDatabase.getName()); - findAndMarkDatabaseAsOrphan(restoreDatabase.getClassifiers(), type); - String adapterId = logicalRestore.getAdapterId(); - String physicalDatabaseId = physicalDatabasesService.getByAdapterId(adapterId).getPhysicalDatabaseIdentifier(); - List ensuredUsers = dbNameToEnsuredUsers.get(restoreDatabase.getName()); - Database newDatabase = createLogicalDatabase( - restoreDatabase.getName(), - restoreDatabase.getSettings(), - restoreDatabase.getClassifiers().stream() - .map(Classifier::getClassifier).collect(Collectors.toSet()), - type, - false, - false, - adapterId, - physicalDatabaseId, - restoreDatabase.getBgVersion()); - - newDatabase.setConnectionProperties(ensuredUsers.stream().map(EnsuredUser::getConnectionProperties).toList()); - newDatabase.setResources(ensuredUsers.stream().map(EnsuredUser::getResources).filter(Objects::nonNull).flatMap(Collection::stream).toList()); - newDatabase.setResources(newDatabase.getResources().stream().distinct().collect(Collectors.toList())); - encryption.encryptPassword(newDatabase); - databaseRegistryDbaasRepository.saveInternalDatabase(newDatabase.getDatabaseRegistry().getFirst()); - log.info("Based restoreDatabase={}, database with id={} created", restoreDatabase.getName(), newDatabase.getId()); - }); - }); - // Creating LogicalDb based externalDbs - restore.getExternalDatabases().forEach(externalDatabase -> { - log.info("Processing externalDatabase={}, type={}", externalDatabase.getName(), externalDatabase.getType()); - String type = externalDatabase.getType(); - findAndMarkDatabaseAsOrphan(externalDatabase.getClassifiers(), type); + log.info("Start creating logical databases from restore {}", restore.getName()); + // Creating LogicalDb based logicalRestores + restore.getLogicalRestores().forEach(logicalRestore -> { + log.info("Processing logicalRestore={}, type={}, adapterId={}", logicalRestore.getLogicalRestoreName(), logicalRestore.getType(), logicalRestore.getAdapterId()); + logicalRestore.getRestoreDatabases().forEach(restoreDatabase -> { + String type = logicalRestore.getType(); + log.info("Processing restoreDatabase={}", restoreDatabase.getName()); + findAndMarkDatabaseAsOrphan(restoreDatabase.getClassifiers(), type); + String adapterId = logicalRestore.getAdapterId(); + String physicalDatabaseId = physicalDatabasesService.getByAdapterId(adapterId).getPhysicalDatabaseIdentifier(); + List ensuredUsers = dbNameToEnsuredUsers.get(restoreDatabase.getName()); Database newDatabase = createLogicalDatabase( - externalDatabase.getName(), - null, - externalDatabase.getClassifiers().stream() + restoreDatabase.getName(), + restoreDatabase.getSettings(), + restoreDatabase.getClassifiers().stream() .map(Classifier::getClassifier).collect(Collectors.toSet()), type, - true, - true, - null, - null, - null); - databaseRegistryDbaasRepository.saveExternalDatabase(newDatabase.getDatabaseRegistry().getFirst()); - log.info("Based externalDb={}, database with id={} created", externalDatabase.getName(), newDatabase.getId()); + false, + false, + adapterId, + physicalDatabaseId, + restoreDatabase.getBgVersion()); + + newDatabase.setConnectionProperties(ensuredUsers.stream().map(EnsuredUser::getConnectionProperties).toList()); + newDatabase.setResources(ensuredUsers.stream().map(EnsuredUser::getResources).filter(Objects::nonNull).flatMap(Collection::stream).toList()); + newDatabase.setResources(newDatabase.getResources().stream().distinct().collect(Collectors.toList())); + encryption.encryptPassword(newDatabase); + databaseRegistryDbaasRepository.saveInternalDatabase(newDatabase.getDatabaseRegistry().getFirst()); + log.info("Based on restoreDatabase={}, database with id={} created", restoreDatabase.getName(), newDatabase.getId()); }); - restore.setStatus(RestoreStatus.COMPLETED); - log.info("Finished initializing logical databases from restore {}", restore.getName()); - } catch (Exception e) { - log.error("Exception occurred during restore process", e); - restore.setStatus(RestoreStatus.FAILED); - restore.setErrorMessage(e.getMessage()); - throw e; - } + }); + // Creating LogicalDb based externalDbs + restore.getExternalDatabases().forEach(externalDatabase -> { + log.info("Processing externalDatabase={}, type={}", externalDatabase.getName(), externalDatabase.getType()); + String type = externalDatabase.getType(); + findAndMarkDatabaseAsOrphan(externalDatabase.getClassifiers(), type); + Database newDatabase = createLogicalDatabase( + externalDatabase.getName(), + null, + externalDatabase.getClassifiers().stream() + .map(Classifier::getClassifier).collect(Collectors.toSet()), + type, + true, + true, + null, + null, + null); + databaseRegistryDbaasRepository.saveExternalDatabase(newDatabase.getDatabaseRegistry().getFirst()); + log.info("Based on externalDb={}, database with id={} created", externalDatabase.getName(), newDatabase.getId()); + }); + restore.setStatus(RestoreStatus.COMPLETED); + log.info("Finished initializing logical databases from restore {}", restore.getName()); } private Set findSimilarDbByClassifier(List classifiers, String type) { @@ -1455,7 +1472,9 @@ private Set findSimilarDbByClassifier(List classifiers, private void findAndMarkDatabaseAsOrphan(List classifiers, String type) { classifiers.stream() - .filter(classifier -> ClassifierType.REPLACED == classifier.getType()) + .filter(classifier -> ClassifierType.REPLACED == classifier.getType() || + ClassifierType.TRANSIENT_REPLACED == classifier.getType() + ) .forEach(classifier -> { SortedMap currClassifier = classifier.getClassifier(); databaseRegistryDbaasRepository @@ -1557,19 +1576,20 @@ public RestoreResponse retryRestore(String restoreName) { Source.builder().build()); } - return restoreLockWrapper(() -> { + Restore retriedRestore = restoreLockWrapper(() -> { retryRestore(restore); aggregateRestoreStatus(restore); restoreRepository.save(restore); - return mapper.toRestoreResponse(restore); + return restore; }); + return mapper.toRestoreResponse(retriedRestore); } private void checkBackupStatusForRestore(String restoreName, BackupStatus status) { if (status != BackupStatus.COMPLETED) { - log.error("Restore {} can't process due to backup status {}", restoreName, status); + log.error("Restore {} can't be processed due to backup status {}", restoreName, status); throw new UnprocessableEntityException( - restoreName, String.format("restore can't process due to backup status %s", status), + restoreName, String.format("Restore can't be processed due to backup status %s", status), Source.builder().build()); } } @@ -1609,10 +1629,10 @@ protected Map> validateAndFilterDatabasesForBac Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) )); - Map> externalDatabases = partitioned.get(true); - Map> internalDatabases = partitioned.get(false); + Map> externalDatabases = partitioned.getOrDefault(true, Map.of()); + Map> internalDatabases = partitioned.getOrDefault(false, Map.of()); - if (externalDatabases != null && !externalDatabases.isEmpty()) { + if (!externalDatabases.isEmpty()) { String externalNames = externalDatabases.keySet().stream() .map(Database::getName) .collect(Collectors.joining(", ")); @@ -1721,15 +1741,16 @@ private T restoreLockWrapper(Supplier action) { } } - private RetryPolicy buildRetryPolicy(String name, String operation) { + private RetryPolicy buildRetryPolicy(String operation, String idType, String id, String adapterId) { + String context = String.format("%s operation [%s=%s, adapter=%s]", operation, idType, id, adapterId); return new RetryPolicy<>() .handle(WebApplicationException.class) .withMaxRetries(retryAttempts) .withDelay(retryDelay) .onFailedAttempt(e -> log.warn("Attempt failed for {}: {}", - name, extractErrorMessage(e.getLastFailure()))) - .onRetry(e -> log.info("Retrying {}...", operation)) - .onFailure(e -> log.error("Request limit exceeded for {}", name)); + context, extractErrorMessage(e.getLastFailure()))) + .onRetry(e -> log.info("Retrying {}...", context)) + .onFailure(e -> log.error("Request limit exceeded for {}", context)); } private String extractErrorMessage(Throwable throwable) { From 76838e3b1235b7a963ce24c87cc3de7ff6e74dd2 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 30 Jan 2026 15:56:12 +0500 Subject: [PATCH 34/46] chore: remove unused imports --- .../com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java | 1 - .../dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java | 2 -- .../com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java | 1 - .../netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java | 1 - 4 files changed, 5 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java index 506e29d4..340d3097 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; -import jakarta.validation.groups.Default; import lombok.Data; import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.media.Schema; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index f95860db..04b4a319 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -1,10 +1,8 @@ package com.netcracker.cloud.dbaas.dto.backupV2; -import com.netcracker.cloud.core.error.rest.tmf.TmfErrorResponse; import lombok.Data; import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; -import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.Schema; import java.util.List; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java index 4b023f48..74f1aff9 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreRequest.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; -import jakarta.validation.groups.Default; import lombok.Data; import lombok.NoArgsConstructor; import org.eclipse.microprofile.openapi.annotations.media.Schema; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java index 63edc940..991c49ab 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreResponse.java @@ -7,7 +7,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.groups.ConvertGroup; -import jakarta.validation.groups.Default; import lombok.AllArgsConstructor; import lombok.Data; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; From ed2b81ef39051101cedfa6b39d55ae108b56494e Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 30 Jan 2026 15:57:09 +0500 Subject: [PATCH 35/46] refactor: invert methods --- .../dbaas/service/DbBackupV2Service.java | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index c4f0b431..e477d053 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -195,7 +195,7 @@ protected Backup initializeFullBackupStructure(Map> databaseToRegistry, Backup backup) { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(adapterId); - if (!isBackupRestoreSupported(adapter)) { + if (isBackupRestoreUnsupported(adapter)) { log.error("Adapter {} does not support backup operation", adapterId); throw new DatabaseBackupRestoreNotSupportedException( String.format("Adapter %s does not support backup operation", adapterId), @@ -252,7 +252,6 @@ protected void startBackup(Backup backup) { .toList(); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - backupRepository.save(backup); } protected LogicalBackupAdapterResponse startLogicalBackup(LogicalBackup logicalBackup) { @@ -450,7 +449,7 @@ protected Map> getAllDbByFilter(FilterCriteria if (!isValidRegistry(registry)) return false; return filterCriteria.getExclude().stream() - .filter(exclude -> !isEmpty(exclude)) + .filter(this::isFilled) .noneMatch(exclude -> { boolean configurational = registry.getBgVersion() != null && !registry.getBgVersion().isBlank(); return isMatches(exclude, @@ -499,11 +498,11 @@ private boolean isMatches(Filter filter, String namespace, String microserviceNa return true; } - private boolean isEmpty(Filter f) { - return f.getNamespace().isEmpty() - && f.getMicroserviceName().isEmpty() - && f.getDatabaseType().isEmpty() - && f.getDatabaseKind().isEmpty(); + private boolean isFilled(Filter f) { + return !f.getNamespace().isEmpty() + || !f.getMicroserviceName().isEmpty() + || !f.getDatabaseType().isEmpty() + || !f.getDatabaseKind().isEmpty(); } private boolean isKindMatched(boolean configurational, DatabaseKind kind) { @@ -671,26 +670,36 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, Backup backup = getBackupOrThrowException(backupName); checkBackupStatusForRestore(restoreName, backup.getStatus()); + if (dryRun) { + return applyDryRunRestore(backup, restoreRequest); + } + Restore restore = restoreLockWrapper(() -> { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); - if (!dryRun) - restoreRepository.save(currRestore); + restoreRepository.save(currRestore); return currRestore; }); // DryRun on adapters startRestore(restore, true); aggregateRestoreStatus(restore); - if (!dryRun && RestoreStatus.FAILED != restore.getStatus()) { + if (RestoreStatus.FAILED != restore.getStatus()) { // Real run on adapters startRestore(restore, false); aggregateRestoreStatus(restore); } - if (!dryRun) - restoreRepository.save(restore); + + restoreRepository.save(restore); return mapper.toRestoreResponse(restore); } + private RestoreResponse applyDryRunRestore(Backup backup, RestoreRequest restoreRequest) { + Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); + // DryRun on adapters + startRestore(currRestore, true); + aggregateRestoreStatus(currRestore); + return mapper.toRestoreResponse(currRestore); + } protected List getAllDbByFilter(List backupDatabasesToFilter, FilterCriteria filterCriteria) { if (isFilterEmpty(filterCriteria)) @@ -714,7 +723,7 @@ protected List getAllDbByFilter(List bac String type = db.getLogicalBackup().getType(); boolean configurational = db.isConfigurational(); return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) - && filterCriteria.getExclude().stream().filter(exclude -> !isEmpty(exclude)).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); + && filterCriteria.getExclude().stream().filter(this::isFilled).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList(); @@ -945,7 +954,7 @@ private void collectDuplicateClassifiers( String microserviceName = (String) classifier.get(MICROSERVICE_NAME); String type = db.getType(); return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, false)) - && filterCriteria.getExclude().stream().filter(exclude -> !isEmpty(exclude)).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); + && filterCriteria.getExclude().stream().filter(this::isFilled).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); }) .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList(); @@ -1060,7 +1069,7 @@ private Map.Entry mapToAdapterBackupKe // Checking adapter support backup restore DbaasAdapter adapter = physicalDatabasesService.getAdapterById(adapterId); - if (!isBackupRestoreSupported(adapter)) { + if (isBackupRestoreUnsupported(adapter)) { throw new DatabaseBackupRestoreNotSupportedException( String.format("Adapter %s does not support restore operation", adapterId), Source.builder().build()); @@ -1659,7 +1668,7 @@ protected Map> validateAndFilterDatabasesForBac return true; } if (db.getAdapterId() != null) { - return !isBackupRestoreSupported(physicalDatabasesService.getAdapterById(db.getAdapterId())); + return isBackupRestoreUnsupported(physicalDatabasesService.getAdapterById(db.getAdapterId())); } return false; }) @@ -1693,8 +1702,8 @@ protected Map> validateAndFilterDatabasesForBac return filteredDatabases; } - private boolean isBackupRestoreSupported(DbaasAdapter adapter) { - return adapter.isBackupRestoreSupported(); + private boolean isBackupRestoreUnsupported(DbaasAdapter adapter) { + return !adapter.isBackupRestoreSupported(); } private void backupExistenceCheck(String backupName) { From ec959f2e8f7b55a5b1a893727f1f2a690547a9f4 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 30 Jan 2026 18:35:43 +0500 Subject: [PATCH 36/46] refactor: replace entity builder with constructor --- .../entity/pg/backupV2/BackupDatabase.java | 9 ++++ .../entity/pg/backupV2/LogicalBackup.java | 11 +++-- .../dbaas/service/DbBackupV2Service.java | 42 +++++++++---------- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java index c56282c7..ffb08ce1 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java @@ -76,4 +76,13 @@ public static class User { String name; String role; } + + public BackupDatabase(LogicalBackup logicalBackup, String name, List> classifiers, Map settings, List users, boolean configurational) { + this.logicalBackup = logicalBackup; + this.name = name; + this.classifiers = classifiers; + this.settings = settings; + this.users = users; + this.configurational = configurational; + } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java index 16380155..53b2de5c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java @@ -5,9 +5,9 @@ import com.netcracker.cloud.dbaas.enums.BackupTaskStatus; import jakarta.persistence.*; import lombok.*; -import org.eclipse.microprofile.openapi.annotations.media.Schema; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -22,7 +22,6 @@ public class LogicalBackup { @Id @GeneratedValue - @Schema(description = "A unique identifier of the logical backup process.", required = true) private UUID id; @Column(name = "logical_backup_name") @@ -41,7 +40,7 @@ public class LogicalBackup { @ToString.Exclude @JsonManagedReference @OneToMany(mappedBy = "logicalBackup", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - private List backupDatabases; + private List backupDatabases = new ArrayList<>(); @Enumerated(EnumType.STRING) @Column(name = "status") @@ -56,6 +55,12 @@ public class LogicalBackup { @Column(name = "completion_time") private Instant completionTime; + public LogicalBackup(Backup backup, String adapterId, String type) { + this.backup = backup; + this.adapterId = adapterId; + this.type = type; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index e477d053..8f56b89e 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -202,26 +202,21 @@ private LogicalBackup createLogicalBackup(String adapterId, Map()) - .build(); + LogicalBackup logicalBackup = new LogicalBackup(backup, adapterId, adapter.type()); + // Initializing backup database entity logicalBackup.getBackupDatabases().addAll(databaseToRegistry.entrySet().stream() .map(entry -> { Database db = entry.getKey(); List databaseRegistries = entry.getValue(); - return BackupDatabase.builder() - .logicalBackup(logicalBackup) - .name(DbaasBackupUtils.getDatabaseName(db)) - .classifiers(databaseRegistries.stream() - .map(DatabaseRegistry::getClassifier).toList()) - .users(getBackupDatabaseUsers(db.getConnectionProperties())) - .settings(db.getSettings()) - .configurational(db.getBgVersion() != null && !db.getBgVersion().isBlank()) - .build(); + return new BackupDatabase( + logicalBackup, + DbaasBackupUtils.getDatabaseName(db), + databaseRegistries.stream().map(DatabaseRegistry::getClassifier).toList(), + db.getSettings(), + getBackupDatabaseUsers(db.getConnectionProperties()), + db.getBgVersion() != null && !db.getBgVersion().isBlank() + ); }).toList()); return logicalBackup; } @@ -1004,14 +999,15 @@ private List createRestoreDatabases( .map(u -> new RestoreDatabase.User(u.getName(), u.getRole())) .toList(); - return RestoreDatabase.builder() - .backupDatabase(backupDatabase) - .name(backupDatabase.getName()) - .classifiers(classifiers) - .settings(backupDatabase.getSettings()) - .users(users) - .bgVersion(bgVersion) - .build(); + RestoreDatabase restoreDatabase = new RestoreDatabase(); + restoreDatabase.setBackupDatabase(backupDatabase); + restoreDatabase.setName(backupDatabase.getName()); + restoreDatabase.setClassifiers(classifiers); + restoreDatabase.setSettings(backupDatabase.getSettings()); + restoreDatabase.setUsers(users); + restoreDatabase.setBgVersion(bgVersion); + + return restoreDatabase; }) .toList(); } From d313d81751e02059abcd8233ead3c6f1717613ae Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 2 Feb 2026 12:51:43 +0500 Subject: [PATCH 37/46] refactor: replace entity builder with constructor --- .../dbaas/entity/pg/backupV2/Backup.java | 6 +- .../entity/pg/backupV2/BackupDatabase.java | 3 - .../pg/backupV2/BackupExternalDatabase.java | 9 +- .../pg/backupV2/FilterCriteriaEntity.java | 3 - .../entity/pg/backupV2/FilterEntity.java | 2 - .../entity/pg/backupV2/LogicalBackup.java | 6 +- .../entity/pg/backupV2/LogicalRestore.java | 6 +- .../dbaas/entity/pg/backupV2/Restore.java | 7 +- .../entity/pg/backupV2/RestoreDatabase.java | 2 - .../pg/backupV2/RestoreExternalDatabase.java | 2 - .../repositories/pg/jpa/BackupRepository.java | 3 +- .../pg/jpa/RestoreRepository.java | 3 +- .../dbaas/service/DbBackupV2Service.java | 24 +- .../dbaas/service/DbBackupV2ServiceTest.java | 244 +++++++++--------- docs/OpenAPI.json | 183 +++++++------ 15 files changed, 253 insertions(+), 250 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Backup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Backup.java index ca2e0f48..f436454d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Backup.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Backup.java @@ -5,7 +5,10 @@ import com.netcracker.cloud.dbaas.enums.ExternalDatabaseStrategy; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; @@ -13,7 +16,6 @@ @Data -@Builder @AllArgsConstructor @NoArgsConstructor(force = true) @Entity diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java index ffb08ce1..b8d7a73a 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java @@ -5,7 +5,6 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.JdbcTypeCode; @@ -18,7 +17,6 @@ import java.util.UUID; @Data -@Builder @AllArgsConstructor @NoArgsConstructor(force = true) @Entity @@ -69,7 +67,6 @@ public class BackupDatabase { private Instant creationTime; @Data - @Builder @NoArgsConstructor @AllArgsConstructor public static class User { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java index 7b1fff0b..73703aba 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java @@ -4,7 +4,6 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.JdbcTypeCode; @@ -15,7 +14,6 @@ import java.util.UUID; @Data -@Builder @AllArgsConstructor @NoArgsConstructor(force = true) @Entity @@ -41,4 +39,11 @@ public class BackupExternalDatabase { @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private List> classifiers; + + public BackupExternalDatabase(Backup backup, String name, String type, List> classifiers) { + this.backup = backup; + this.name = name; + this.type = type; + this.classifiers = classifiers; + } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java index 1988ffc7..3412b59b 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java @@ -1,14 +1,11 @@ package com.netcracker.cloud.dbaas.entity.pg.backupV2; -import lombok.Builder; import lombok.Data; import java.util.List; @Data -@Builder public class FilterCriteriaEntity { private List filter; - private List include; private List exclude; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterEntity.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterEntity.java index 66a256c4..e4d8ccde 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterEntity.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterEntity.java @@ -2,13 +2,11 @@ import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseKind; import com.netcracker.cloud.dbaas.dto.backupV2.DatabaseType; -import lombok.Builder; import lombok.Data; import java.util.List; @Data -@Builder public class FilterEntity { private List namespace; private List microserviceName; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java index 53b2de5c..eb0b983b 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java @@ -4,7 +4,10 @@ import com.fasterxml.jackson.annotation.JsonManagedReference; import com.netcracker.cloud.dbaas.enums.BackupTaskStatus; import jakarta.persistence.*; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; import java.time.Instant; import java.util.ArrayList; @@ -13,7 +16,6 @@ import java.util.UUID; @Data -@Builder @NoArgsConstructor @AllArgsConstructor @Entity diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java index 495fc028..b8c5fb87 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java @@ -2,7 +2,10 @@ import com.netcracker.cloud.dbaas.enums.RestoreTaskStatus; import jakarta.persistence.*; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; import java.time.Instant; import java.util.List; @@ -10,7 +13,6 @@ import java.util.UUID; @Data -@Builder @AllArgsConstructor @NoArgsConstructor(force = true) @Entity diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java index f86ecdbc..6a99de64 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java @@ -5,7 +5,10 @@ import com.netcracker.cloud.dbaas.enums.RestoreStatus; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; @@ -14,7 +17,6 @@ import java.util.Objects; @Data -@Builder @AllArgsConstructor @NoArgsConstructor(force = true) @Entity @@ -74,7 +76,6 @@ public class Restore { private int attemptCount = 0; @Data - @Builder @NoArgsConstructor @AllArgsConstructor public static class MappingEntity { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java index 1a35a75c..e0e52a04 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java @@ -4,7 +4,6 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.JdbcTypeCode; @@ -16,7 +15,6 @@ import java.util.UUID; @Data -@Builder @AllArgsConstructor @NoArgsConstructor(force = true) @Entity diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java index d3327d21..f3ab19aa 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java @@ -4,7 +4,6 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.JdbcTypeCode; @@ -14,7 +13,6 @@ import java.util.UUID; @Data -@Builder @AllArgsConstructor @NoArgsConstructor(force = true) @Entity diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java index 43396a08..ebe26b3e 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java @@ -15,8 +15,7 @@ public class BackupRepository implements PanacheRepositoryBase { public Backup save(Backup backup) { EntityManager entityManager = getEntityManager(); - entityManager.merge(backup); - return backup; + return entityManager.merge(backup); } public List findBackupsToAggregate() { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java index 0822f451..dd66cb03 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java @@ -15,8 +15,7 @@ public class RestoreRepository implements PanacheRepositoryBase public Restore save(Restore restore) { EntityManager entityManager = getEntityManager(); - entityManager.merge(restore); - return restore; + return entityManager.merge(restore); } public List findRestoresToAggregate() { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 8f56b89e..c70043b4 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -126,13 +126,13 @@ public BackupResponse backup(BackupRequest backupRequest, boolean dryRun) { Backup backup = initializeFullBackupStructure(filteredDb, backupRequest); if (!dryRun) { // Saving initialized backup structure - backupRepository.save(backup); + backup = backupRepository.save(backup); startBackup(backup); } updateAggregatedStatus(backup); if (!dryRun) { // Saving aggregated backup structure - backupRepository.save(backup); + backup = backupRepository.save(backup); } return mapper.toBackupResponse(backup); } @@ -176,14 +176,14 @@ protected Backup initializeFullBackupStructure(Map { Database database = entry.getKey(); List databaseRegistries = entry.getValue(); - return BackupExternalDatabase.builder() - .backup(backup) - .name(database.getName()) - .type(database.getDatabaseRegistry().getFirst().getType()) - .classifiers(databaseRegistries.stream() + return new BackupExternalDatabase( + backup, + database.getName(), + database.getDatabaseRegistry().getFirst().getType(), + databaseRegistries.stream() .map(DatabaseRegistry::getClassifier) - .toList()) - .build(); + .toList() + ); }).toList(); // Persist and return @@ -312,7 +312,7 @@ public void checkBackupsAsync() { backupsToAggregate.forEach(this::trackAndAggregate); } - protected void trackAndAggregate(Backup backup) { + protected void trackAndAggregate(Backup backup) { if (backup.getAttemptCount() > retryCount) { log.warn("The number of attempts to track backup {} exceeded {}", backup.getName(), retryCount); backup.setStatus(BackupStatus.FAILED); @@ -671,8 +671,7 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, Restore restore = restoreLockWrapper(() -> { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); - restoreRepository.save(currRestore); - return currRestore; + return restoreRepository.save(currRestore); }); // DryRun on adapters @@ -690,6 +689,7 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, private RestoreResponse applyDryRunRestore(Backup backup, RestoreRequest restoreRequest) { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); + currRestore.getLogicalRestores().forEach(db -> db.setId(UUID.randomUUID())); // DryRun on adapters startRestore(currRestore, true); aggregateRestoreStatus(currRestore); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index b8234650..b431d537 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -3077,7 +3077,7 @@ void trackAndAggregate_backupFinishedWithStatusFailed() { .findAny() .ifPresent(db -> db.setStatus(BackupTaskStatus.FAILED)); }); - + Backup updatedBackup = backupRepository.save(backup); DbaasAdapter adapter1 = Mockito.mock(DbaasAdapter.class); when(physicalDatabasesService.getAdapterById("0")) @@ -3085,7 +3085,7 @@ void trackAndAggregate_backupFinishedWithStatusFailed() { when(adapter1.trackBackupV2("logicalBackupName0", "storageName", "blobPath")) .thenReturn(adapterResponse); - dbBackupV2Service.trackAndAggregate(backup); + dbBackupV2Service.trackAndAggregate(updatedBackup); Backup expectedBackup = backupRepository.findById(backupName); assertNotNull(expectedBackup); @@ -3093,7 +3093,7 @@ void trackAndAggregate_backupFinishedWithStatusFailed() { String errorMsg = String.format("LogicalBackup %s failed: %s=Error during backup process", "logicalBackupName1", db3Name); assertEquals(errorMsg, expectedBackup.getErrorMessage()); - assertEquals(1, backup.getAttemptCount()); + assertEquals(1, expectedBackup.getAttemptCount()); } @Test @@ -3462,7 +3462,7 @@ void uploadBackupMetadata_restoreDeletedBackup_backupNotImported() { () -> dbBackupV2Service.uploadBackupMetadata(backupResponse)); assertEquals( - String.format("Resource has illegal state: can't restore %s backup that not imported", + String.format("Resource has illegal state: can't restore a %s backup that is not imported", BackupStatus.DELETED), ex.getDetail()); } @@ -3603,11 +3603,11 @@ private Classifier getClassifier(ClassifierType classifierType, String namespace } private BackupExternalDatabase getBackupExternalDatabase(String name, String type, List> classifiers) { - return BackupExternalDatabase.builder() - .name(name) - .type(type) - .classifiers(classifiers) - .build(); + BackupExternalDatabase externalDatabase = new BackupExternalDatabase(); + externalDatabase.setName(name); + externalDatabase.setType(type); + externalDatabase.setClassifiers(classifiers); + return externalDatabase; } private BackupDatabase getBackupDatabase(String dbName, @@ -3615,18 +3615,18 @@ private BackupDatabase getBackupDatabase(String dbName, boolean configurational, BackupTaskStatus status, String errorMessage) { - return BackupDatabase.builder() - .name(dbName) - .classifiers(classifiers) - .settings(Map.of("setting", "setting")) - .users(List.of(new BackupDatabase.User("username", "admin"))) - .configurational(configurational) - .status(status) - .size(1) - .duration(1) - .path("path") - .errorMessage(errorMessage) - .build(); + BackupDatabase backupDatabase = new BackupDatabase(); + backupDatabase.setName(dbName); + backupDatabase.setClassifiers(classifiers); + backupDatabase.setSettings(Map.of("setting", "setting")); + backupDatabase.setUsers(List.of(new BackupDatabase.User("username", "admin"))); + backupDatabase.setConfigurational(configurational); + backupDatabase.setStatus(status); + backupDatabase.setSize(1); + backupDatabase.setDuration(1); + backupDatabase.setPath("path"); + backupDatabase.setErrorMessage(errorMessage); + return backupDatabase; } private LogicalBackup getLogicalBackup(String logicalBackupName, @@ -3636,14 +3636,13 @@ private LogicalBackup getLogicalBackup(String logicalBackupName, BackupTaskStatus status, String errorMsg ) { - LogicalBackup logicalBackup = LogicalBackup.builder() - .logicalBackupName(logicalBackupName) - .adapterId(adapterId) - .type(type) - .backupDatabases(backupDatabases) - .status(status) - .errorMessage(errorMsg) - .build(); + LogicalBackup logicalBackup = new LogicalBackup(); + logicalBackup.setLogicalBackupName(logicalBackupName); + logicalBackup.setAdapterId(adapterId); + logicalBackup.setType(type); + logicalBackup.setBackupDatabases(backupDatabases); + logicalBackup.setStatus(status); + logicalBackup.setErrorMessage(errorMsg); backupDatabases.forEach(db -> db.setLogicalBackup(logicalBackup)); return logicalBackup; @@ -3657,26 +3656,25 @@ private Backup getBackup(String name, BackupStatus status, String errorMsg ) { - Backup backup = Backup.builder() - .name(name) - .storageName(STORAGE_NAME) - .blobPath(BLOB_PATH) - .externalDatabaseStrategy(strategy) - .filterCriteria(filterCriteria) - .logicalBackups(logicalBackups) - .externalDatabases(externalDatabases) - .status(status) - .total(logicalBackups.stream().mapToInt(db -> db.getBackupDatabases().size()).sum()) - .completed((int) logicalBackups.stream() + Backup backup = new Backup(); + backup.setName(name); + backup.setStorageName(STORAGE_NAME); + backup.setBlobPath(BLOB_PATH); + backup.setExternalDatabaseStrategy(strategy); + backup.setFilterCriteria(filterCriteria); + backup.setLogicalBackups(logicalBackups); + backup.setExternalDatabases(externalDatabases); + backup.setStatus(status); + backup.setTotal(logicalBackups.stream().mapToInt(db -> db.getBackupDatabases().size()).sum()); + backup.setCompleted((int) logicalBackups.stream() .flatMap(db -> db.getBackupDatabases().stream()) .filter(bd -> BackupTaskStatus.COMPLETED == bd.getStatus()) - .count()) - .size(logicalBackups.stream() + .count()); + backup.setSize(logicalBackups.stream() .flatMap(db -> db.getBackupDatabases().stream()) .mapToLong(BackupDatabase::getSize) - .sum()) - .errorMessage(errorMsg) - .build(); + .sum()); + backup.setErrorMessage(errorMsg); logicalBackups.forEach(db -> db.setBackup(backup)); externalDatabases.forEach(db -> db.setBackup(backup)); @@ -3689,27 +3687,22 @@ private Backup getBackup(String backupName, String namespace) { List backupDatabases = new ArrayList<>(); for (int i = 0; i < 3; i++) { - BackupDatabase backupDatabase = BackupDatabase.builder() - .name("db" + i) - .users(List.of( - BackupDatabase.User.builder() - .name("username") - .role("role") - .build() - )) - .path("path") - .classifiers(List.of(classifier)) - .build(); + BackupDatabase backupDatabase = new BackupDatabase(); + backupDatabase.setName("db" + i); + backupDatabase.setUsers(List.of(new BackupDatabase.User("username", "role"))); + backupDatabase.setStatus(BackupTaskStatus.COMPLETED); + backupDatabase.setPath("path"); + backupDatabase.setClassifiers(List.of(classifier)); backupDatabases.add(backupDatabase); } List logicalBackups = new ArrayList<>(); for (int i = 0; i < 2; i++) { - LogicalBackup logicalBackup = LogicalBackup.builder() - .logicalBackupName("logicalBackupName" + i) - .adapterId(Integer.toString(i)) - .type("postgresql") - .build(); + LogicalBackup logicalBackup = new LogicalBackup(); + logicalBackup.setLogicalBackupName("logicalBackupName" + i); + logicalBackup.setStatus(BackupTaskStatus.COMPLETED); + logicalBackup.setAdapterId(Integer.toString(i)); + logicalBackup.setType("postgresql"); logicalBackups.add(logicalBackup); } @@ -3724,12 +3717,11 @@ private Backup getBackup(String backupName, String namespace) { secondLogical.setBackupDatabases(second); second.forEach(db -> db.setLogicalBackup(secondLogical)); - FilterEntity filter = FilterEntity.builder() - .namespace(List.of(namespace)) - .build(); - FilterCriteriaEntity criteriaEntity = FilterCriteriaEntity.builder() - .filter(List.of(filter)) - .build(); + FilterEntity filter = new FilterEntity(); + filter.setNamespace(List.of(namespace)); + + FilterCriteriaEntity criteriaEntity = new FilterCriteriaEntity(); + criteriaEntity.setFilter(List.of(filter)); Backup backup = new Backup(); backup.setName(backupName); @@ -3752,24 +3744,22 @@ private Restore getRestore(String restoreName, String namespace) { List restoreDatabases = new ArrayList<>(); for (int i = 0; i < 3; i++) { - RestoreDatabase restoreDatabase = RestoreDatabase.builder() - .name("db" + i) - .users(List.of( - new RestoreDatabase.User("username", "admin") - )) - .path("path") - .classifiers(List.of(classifierMapper)) - .build(); + RestoreDatabase restoreDatabase = new RestoreDatabase(); + restoreDatabase.setName("db" + i); + restoreDatabase.setUsers(List.of(new RestoreDatabase.User("username", "admin"))); + restoreDatabase.setStatus(RestoreTaskStatus.COMPLETED); + restoreDatabase.setPath("path"); + restoreDatabase.setClassifiers(List.of(classifierMapper)); restoreDatabases.add(restoreDatabase); } List logicalRestores = new ArrayList<>(); for (int i = 0; i < 2; i++) { - LogicalRestore logicalRestore = LogicalRestore.builder() - .logicalRestoreName("logicalRestoreName" + i) - .adapterId(Integer.toString(i)) - .type("postgresql") - .build(); + LogicalRestore logicalRestore = new LogicalRestore(); + logicalRestore.setLogicalRestoreName("logicalRestoreName" + i); + logicalRestore.setStatus(RestoreTaskStatus.COMPLETED); + logicalRestore.setAdapterId(Integer.toString(i)); + logicalRestore.setType("postgresql"); logicalRestores.add(logicalRestore); } @@ -3784,12 +3774,10 @@ private Restore getRestore(String restoreName, String namespace) { secondLogical.setRestoreDatabases(second); second.forEach(db -> db.setLogicalRestore(secondLogical)); - FilterEntity filter = FilterEntity.builder() - .namespace(List.of(namespace)) - .build(); - FilterCriteriaEntity criteriaEntity = FilterCriteriaEntity.builder() - .filter(List.of(filter)) - .build(); + FilterEntity filter = new FilterEntity(); + filter.setNamespace(List.of(namespace)); + FilterCriteriaEntity criteriaEntity = new FilterCriteriaEntity(); + criteriaEntity.setFilter(List.of(filter)); Restore restore = new Restore(); restore.setName(restoreName); @@ -3846,17 +3834,17 @@ private RestoreDatabase getRestoreDatabase(BackupDatabase backupDatabase, RestoreTaskStatus status, long duration, String errorMessage) { - return RestoreDatabase.builder() - .backupDatabase(backupDatabase) - .name(dbName) - .classifiers(classifiers) - .settings(settings) - .users(List.of(new RestoreDatabase.User("username", "admin"))) - .bgVersion(bgVersion) - .status(status) - .duration(duration) - .errorMessage(errorMessage) - .build(); + RestoreDatabase db = new RestoreDatabase(); + db.setBackupDatabase(backupDatabase); + db.setName(dbName); + db.setClassifiers(classifiers); + db.setSettings(settings); + db.setUsers(List.of(new RestoreDatabase.User("username", "admin"))); + db.setBgVersion(bgVersion); + db.setStatus(status); + db.setDuration(duration); + db.setErrorMessage(errorMessage); + return db; } private LogicalRestore getLogicalRestore(String logicalRestoreName, @@ -3865,14 +3853,13 @@ private LogicalRestore getLogicalRestore(String logicalRestoreName, List restoreDatabases, RestoreTaskStatus status, String errorMsg) { - LogicalRestore logicalRestore = LogicalRestore.builder() - .logicalRestoreName(logicalRestoreName) - .adapterId(adapterId) - .type(type) - .restoreDatabases(restoreDatabases) - .status(status) - .errorMessage(errorMsg) - .build(); + LogicalRestore logicalRestore = new LogicalRestore(); + logicalRestore.setLogicalRestoreName(logicalRestoreName); + logicalRestore.setAdapterId(adapterId); + logicalRestore.setType(type); + logicalRestore.setRestoreDatabases(restoreDatabases); + logicalRestore.setStatus(status); + logicalRestore.setErrorMessage(errorMsg); restoreDatabases.forEach(db -> db.setLogicalRestore(logicalRestore)); return logicalRestore; @@ -3887,19 +3874,19 @@ private Restore getRestore(Backup backup, List externalDatabases, RestoreStatus status, String errorMsg) { - Restore restore = Restore.builder() - .name(name) - .backup(backup) - .storageName(STORAGE_NAME) - .blobPath(BLOB_PATH) - .filterCriteria(filterCriteria) - .mapping(mapping) - .logicalRestores(logicalRestores) - .externalDatabaseStrategy(strategy) - .externalDatabases(externalDatabases) - .status(status) - .errorMessage(errorMsg) - .build(); + + Restore restore = new Restore(); + restore.setName(name); + restore.setBackup(backup); + restore.setStorageName(STORAGE_NAME); + restore.setBlobPath(BLOB_PATH); + restore.setFilterCriteria(filterCriteria); + restore.setMapping(mapping); + restore.setLogicalRestores(logicalRestores); + restore.setExternalDatabaseStrategy(strategy); + restore.setExternalDatabases(externalDatabases); + restore.setStatus(status); + restore.setErrorMessage(errorMsg); logicalRestores.forEach(db -> db.setRestore(restore)); externalDatabases.forEach(db -> db.setRestore(restore)); @@ -3911,11 +3898,11 @@ private RestoreExternalDatabase getRestoreExternalDb( String type, List classifiers ) { - return RestoreExternalDatabase.builder() - .name(name) - .type(type) - .classifiers(classifiers) - .build(); + RestoreExternalDatabase db = new RestoreExternalDatabase(); + db.setName(name); + db.setType(type); + db.setClassifiers(classifiers); + return db; } private BackupRequest getBackupRequest(String backupName, @@ -4032,12 +4019,11 @@ private BackupResponse getBackupResponse(String backupName, String namespace) { } private FilterCriteriaEntity getFilterCriteriaEntity(List namespaces) { - FilterEntity filter = FilterEntity.builder() - .namespace(namespaces) - .build(); + FilterEntity filter = new FilterEntity(); + filter.setNamespace(namespaces); - return FilterCriteriaEntity.builder() - .filter(List.of(filter)) - .build(); + FilterCriteriaEntity filterCriteria = new FilterCriteriaEntity(); + filterCriteria.setFilter(List.of(filter)); + return filterCriteria; } } diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index d1e23193..ac93484f 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -173,7 +173,9 @@ "settings": { "type": "object", "examples": [ - "{\"key\":value, \"key\":value}" + { + "key": "value" + } ], "additionalProperties": {}, "description": "Database settings as a key-value map" @@ -181,7 +183,12 @@ "users": { "type": "array", "examples": [ - "[{\"name\":\"username\",\"role\":\"admin\"}" + [ + { + "name": "username", + "role": "admin" + } + ] ], "items": { "$ref": "#/components/schemas/User" @@ -306,7 +313,7 @@ "before-prod-update-20251013T1345-G5s8" ], "pattern": "\\S", - "description": "Unique identifier of the backup" + "description": "Unique name of the backup" }, "storageName": { "type": "string", @@ -326,9 +333,6 @@ }, "filterCriteria": { "type": "object", - "required": [ - "filter" - ], "description": "Filter criteria", "properties": { "filter": { @@ -368,7 +372,7 @@ "examples": [ false ], - "description": "Whether non-backupable databases should be ignored during backup", + "description": "Whether non‑backupable databases were ignored during backup", "default": false } } @@ -394,7 +398,7 @@ "before-prod-update-20251013T1345-G5s8" ], "pattern": "\\S", - "description": "Unique identifier of the backup" + "description": "Unique name of the backup" }, "storageName": { "type": "string", @@ -432,13 +436,10 @@ "examples": [ false ], - "description": "Whether external databases were skipped during the backup" + "description": "Whether non‑backupable databases were ignored during backup" }, "filterCriteria": { "type": "object", - "required": [ - "filter" - ], "description": "Filter criteria", "properties": { "filter": { @@ -1806,9 +1807,6 @@ }, "FilterCriteria": { "type": "object", - "required": [ - "filter" - ], "description": "Filter criteria", "properties": { "filter": { @@ -3125,7 +3123,9 @@ "settings": { "type": "object", "examples": [ - "{\"key\":value, \"key\":value}" + { + "key": "value" + } ], "additionalProperties": {}, "description": "Database settings as a key-value map" @@ -3170,7 +3170,7 @@ "examples": [ "Restore Not Found" ], - "description": "Error message if the backup failed" + "description": "Error message if the restore failed" }, "creationTime": { "$ref": "#/components/schemas/Instant", @@ -3205,19 +3205,10 @@ }, "classifiers": { "type": "array", - "examples": [ - [ - { - "namespace": "namespace", - "microserviceName": "microserviceName", - "scope": "service" - } - ] - ], "items": { "$ref": "#/components/schemas/ClassifierResponse" }, - "description": "List of database classifiers. Each classifier is a sorted map of attributes." + "description": "List of classifier objects describing database attributes." } } }, @@ -3260,7 +3251,7 @@ "restore-before-prod-update-20251203T1020-4t6S" ], "pattern": "\\S", - "description": "Unique identifier of the restore" + "description": "Unique name of the restore" }, "storageName": { "type": "string", @@ -3280,9 +3271,6 @@ }, "filterCriteria": { "type": "object", - "required": [ - "filter" - ], "description": "Filter criteria", "properties": { "filter": { @@ -3368,7 +3356,7 @@ "restore-before-prod-update-20251203T1020-4t6S" ], "pattern": "\\S", - "description": "Unique identifier of the restore" + "description": "Unique name of the restore" }, "backupName": { "type": "string", @@ -3376,7 +3364,7 @@ "before-prod-update-20251013T1345-G5s8" ], "pattern": "\\S", - "description": "Unique identifier of the backup" + "description": "Unique name of the backup" }, "storageName": { "type": "string", @@ -3410,9 +3398,6 @@ }, "filterCriteria": { "type": "object", - "required": [ - "filter" - ], "description": "Criteria used to filter restore operations", "properties": { "filter": { @@ -4211,7 +4196,7 @@ "/api/backups/v1/backup": { "post": { "summary": "Initiate database backup", - "description": "Starts an asynchronous backup operation for the specified databases. Returns immediately with a backup identifier that can be used to track progress.", + "description": "Starts an asynchronous backup operation for the specified databases. Returns immediately with a backup name that can be used to track progress.", "tags": [ "Backup \u0026 Restore" ], @@ -4227,14 +4212,14 @@ ], "requestBody": { "description": "Backup request", - "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupRequest" } } - } + }, + "required": true }, "responses": { "200": { @@ -4312,16 +4297,6 @@ } } } - }, - "501": { - "description": "The server does not support the functionality required to fulfill the request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TmfErrorResponse" - } - } - } } }, "security": [ @@ -4342,12 +4317,13 @@ ], "parameters": [ { - "description": "Unique identifier of the backup", + "description": "Unique name of the backup", "required": true, "name": "backupName", "in": "path", "schema": { - "type": "string" + "type": "string", + "pattern": "\\S" } } ], @@ -4362,6 +4338,16 @@ } } }, + "400": { + "description": "The request was invalid or cannot be served", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TmfErrorResponse" + } + } + } + }, "401": { "description": "Authentication is required and has failed or has not been provided" }, @@ -4405,12 +4391,13 @@ ], "parameters": [ { - "description": "Unique identifier of the backup", + "description": "Unique name of the backup", "required": true, "name": "backupName", "in": "path", "schema": { - "type": "string" + "type": "string", + "pattern": "\\S" } }, { @@ -4429,14 +4416,24 @@ "204": { "description": "Backup deleted successfully" }, + "400": { + "description": "The request was invalid or cannot be served", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TmfErrorResponse" + } + } + } + }, "401": { "description": "Authentication is required and has failed or has not been provided" }, "403": { "description": "The request was valid, but the server is refusing action" }, - "404": { - "description": "The requested resource could not be found", + "422": { + "description": "The request was accepted, but the server couldn\u0027t process due to incompatible resource", "content": { "application/json": { "schema": { @@ -4474,7 +4471,7 @@ ], "parameters": [ { - "description": "Unique identifier of the backup", + "description": "Unique name of the backup", "required": true, "name": "backupName", "in": "path", @@ -4506,6 +4503,16 @@ } } }, + "400": { + "description": "The request was invalid or cannot be served", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TmfErrorResponse" + } + } + } + }, "401": { "description": "Authentication is required and has failed or has not been provided" }, @@ -4555,13 +4562,13 @@ "/api/backups/v1/backup/{backupName}/restore": { "post": { "summary": "Restore from backup", - "description": "Initiate a database restore operation from an existing backup.This operation is asynchronous and returns immediately with a restore identifier that can be used to track progress.Operation is not idempotent", + "description": "Initiate a database restore operation from an existing backup.This operation is asynchronous and returns immediately with a restore name that can be used to track progress.Operation is not idempotent", "tags": [ "Backup \u0026 Restore" ], "parameters": [ { - "description": "Unique identifier of the backup", + "description": "Unique name of the backup", "required": true, "name": "backupName", "in": "path", @@ -4666,16 +4673,6 @@ } } } - }, - "501": { - "description": "The server does not support the functionality required to fulfill the request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TmfErrorResponse" - } - } - } } }, "security": [ @@ -4696,7 +4693,7 @@ ], "parameters": [ { - "description": "Unique identifier of the backup", + "description": "Unique name of the backup", "required": true, "name": "backupName", "in": "path", @@ -4717,6 +4714,16 @@ } } }, + "400": { + "description": "The request was invalid or cannot be served", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TmfErrorResponse" + } + } + } + }, "401": { "description": "Authentication is required and has failed or has not been provided" }, @@ -4756,34 +4763,34 @@ "/api/backups/v1/operation/uploadMetadata": { "post": { "summary": "Upload backup metadata", - "description": "Metadata upload done", + "description": "Upload backup metadata", "tags": [ "Backup \u0026 Restore" ], "parameters": [ { - "description": "Digest header in format: sha-256\u003d\u003cbase64-hash\u003e", + "description": "Digest header in format: SHA-256\u003d\u003cbase64-hash\u003e", "in": "header", "name": "Digest", "required": true, "schema": { "type": "string", "examples": [ - "sha-256\u003dnOJRJg..." + "SHA-256\u003dnOJRJg..." ] } } ], "requestBody": { "description": "Backup metadata", - "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupResponse" } } - } + }, + "required": true }, "responses": { "200": { @@ -4844,7 +4851,7 @@ ], "parameters": [ { - "description": "Unique identifier of the restore operation", + "description": "Unique name of the restore operation", "required": true, "name": "restoreName", "in": "path", @@ -4865,6 +4872,16 @@ } } }, + "400": { + "description": "The request was invalid or cannot be served", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TmfErrorResponse" + } + } + } + }, "401": { "description": "Authentication is required and has failed or has not been provided" }, @@ -4908,12 +4925,13 @@ ], "parameters": [ { - "description": "Unique identifier of the restore operation", + "description": "Unique name of the restore operation", "required": true, "name": "restoreName", "in": "path", "schema": { - "type": "string" + "type": "string", + "pattern": "\\S" } } ], @@ -4927,8 +4945,8 @@ "403": { "description": "The request was valid, but the server is refusing action" }, - "404": { - "description": "The requested resource could not be found", + "422": { + "description": "The request was accepted, but the server couldn\u0027t process due to incompatible resource", "content": { "application/json": { "schema": { @@ -4966,12 +4984,13 @@ ], "parameters": [ { - "description": "Unique identifier of the restore operation", + "description": "Unique name of the restore operation", "required": true, "name": "restoreName", "in": "path", "schema": { - "type": "string" + "type": "string", + "pattern": "\\S" } } ], @@ -5051,7 +5070,7 @@ ], "parameters": [ { - "description": "Unique identifier of the restore operation", + "description": "Unique name of the restore operation", "required": true, "name": "restoreName", "in": "path", From 8d49e7479ec8e2aa0870178a3954523f95dca83e Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 2 Feb 2026 16:17:46 +0500 Subject: [PATCH 38/46] feat: replace auto generating id to manually --- .../dto/backupV2/BackupDatabaseResponse.java | 7 +++ .../BackupExternalDatabaseResponse.java | 7 +++ .../dto/backupV2/LogicalBackupResponse.java | 8 ++- .../dto/backupV2/LogicalRestoreResponse.java | 7 +++ .../dto/backupV2/RestoreDatabaseResponse.java | 7 +++ .../RestoreExternalDatabaseResponse.java | 7 +++ .../entity/pg/backupV2/BackupDatabase.java | 4 +- .../pg/backupV2/BackupExternalDatabase.java | 8 --- .../entity/pg/backupV2/LogicalBackup.java | 4 +- .../entity/pg/backupV2/LogicalRestore.java | 1 - .../entity/pg/backupV2/RestoreDatabase.java | 1 - .../pg/backupV2/RestoreExternalDatabase.java | 1 - .../cloud/dbaas/mapper/BackupV2Mapper.java | 2 +- .../dbaas/service/DbBackupV2Service.java | 7 ++- .../v3/DatabaseBackupV2ControllerTest.java | 7 ++- .../dbaas/service/DbBackupV2ServiceTest.java | 13 +++++ docs/OpenAPI.json | 54 +++++++++++++++++++ 17 files changed, 122 insertions(+), 23 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java index 9e965b56..2da0aee3 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java @@ -11,11 +11,18 @@ import java.util.List; import java.util.Map; import java.util.SortedMap; +import java.util.UUID; @Data @AllArgsConstructor @Schema(description = "Logical database backup details") public class BackupDatabaseResponse { + @Schema( + description = "Identifier of the database", + examples = {"550e8400-e29b-41d4-a716-446655440000"}, + required = true + ) + private UUID id; @Schema( description = "Name of the database", examples = { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java index 932881ed..d7924ec1 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java @@ -6,11 +6,18 @@ import java.util.List; import java.util.SortedMap; +import java.util.UUID; @Data @NoArgsConstructor @Schema(description = "External database details") public class BackupExternalDatabaseResponse { + @Schema( + description = "Identifier of the external database", + examples = {"550e8400-e29b-41d4-a716-446655440000"}, + required = true + ) + private UUID id; @Schema(description = "Name of the external database", examples = "mydb", required = true) private String name; @Schema( diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalBackupResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalBackupResponse.java index c733c679..5a58f07a 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalBackupResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalBackupResponse.java @@ -8,12 +8,18 @@ import java.time.Instant; import java.util.List; +import java.util.UUID; @Data @AllArgsConstructor @Schema(description = "Logical backup details") public class LogicalBackupResponse { - + @Schema( + description = "Identifier of the logical backup", + examples = {"550e8400-e29b-41d4-a716-446655440000"}, + required = true + ) + private UUID id; @Schema(description = "Name of the logical backup in adapter", required = true) String logicalBackupName; @Schema( diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalRestoreResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalRestoreResponse.java index 5374c65b..be68b7b2 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalRestoreResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/LogicalRestoreResponse.java @@ -9,12 +9,19 @@ import java.time.Instant; import java.util.List; +import java.util.UUID; @Data @NoArgsConstructor @AllArgsConstructor @Schema(description = "Logical restore details") public class LogicalRestoreResponse { + @Schema( + description = "Identifier of the logical restore", + examples = {"550e8400-e29b-41d4-a716-446655440000"}, + required = true + ) + private UUID id; @Schema(description = "Name of the logical restore in adapter", required = true) private String logicalRestoreName; @Schema( diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java index ba213389..bb9705fa 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java @@ -9,11 +9,18 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.UUID; @Data @NoArgsConstructor @Schema(description = "Logical database restore details") public class RestoreDatabaseResponse { + @Schema( + description = "Identifier of the database", + examples = {"550e8400-e29b-41d4-a716-446655440000"}, + required = true + ) + private UUID id; @Schema( description = "Name of the database", examples = { diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index 04b4a319..1b54f817 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -6,11 +6,18 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema; import java.util.List; +import java.util.UUID; @Data @NoArgsConstructor @Schema(description = "External database details") public class RestoreExternalDatabaseResponse { + @Schema( + description = "Identifier of the database", + examples = {"550e8400-e29b-41d4-a716-446655440000"}, + required = true + ) + private UUID id; @Schema(description = "Name of the external database", examples = "mydb", required = true) private String name; @Schema(description = "Type of the database", examples = "postgresql") diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java index b8d7a73a..e55dda75 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupDatabase.java @@ -24,7 +24,6 @@ public class BackupDatabase { @Id - @GeneratedValue private UUID id; @ManyToOne @@ -74,7 +73,8 @@ public static class User { String role; } - public BackupDatabase(LogicalBackup logicalBackup, String name, List> classifiers, Map settings, List users, boolean configurational) { + public BackupDatabase(UUID id, LogicalBackup logicalBackup, String name, List> classifiers, Map settings, List users, boolean configurational) { + this.id = id; this.logicalBackup = logicalBackup; this.name = name; this.classifiers = classifiers; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java index 73703aba..d0a952b0 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/BackupExternalDatabase.java @@ -21,7 +21,6 @@ public class BackupExternalDatabase { @Id - @GeneratedValue private UUID id; @ManyToOne @@ -39,11 +38,4 @@ public class BackupExternalDatabase { @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") private List> classifiers; - - public BackupExternalDatabase(Backup backup, String name, String type, List> classifiers) { - this.backup = backup; - this.name = name; - this.type = type; - this.classifiers = classifiers; - } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java index eb0b983b..ceddd62d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalBackup.java @@ -23,7 +23,6 @@ public class LogicalBackup { @Id - @GeneratedValue private UUID id; @Column(name = "logical_backup_name") @@ -57,7 +56,8 @@ public class LogicalBackup { @Column(name = "completion_time") private Instant completionTime; - public LogicalBackup(Backup backup, String adapterId, String type) { + public LogicalBackup(UUID id, Backup backup, String adapterId, String type) { + this.id = id; this.backup = backup; this.adapterId = adapterId; this.type = type; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java index b8c5fb87..7af1d8a8 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/LogicalRestore.java @@ -20,7 +20,6 @@ public class LogicalRestore { @Id - @GeneratedValue private UUID id; @Column(name = "logical_restore_name") diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java index e0e52a04..ec257237 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java @@ -22,7 +22,6 @@ public class RestoreDatabase { @Id - @GeneratedValue private UUID id; @ManyToOne diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java index f3ab19aa..6df00818 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java @@ -20,7 +20,6 @@ public class RestoreExternalDatabase { @Id - @GeneratedValue private UUID id; @ManyToOne diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java index 7e03a3f1..ec136767 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java @@ -98,7 +98,7 @@ private static > R mapStatus( }; } - @Mapping(target = "id", ignore = true) + @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID())") @Mapping(target = "name", source = "backupExternalDatabase.name") @Mapping(target = "type", source = "backupExternalDatabase.type") @Mapping(target = "classifiers", source = "classifiers") diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index c70043b4..f37b6007 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -177,6 +177,7 @@ protected Backup initializeFullBackupStructure(Map databaseRegistries = entry.getValue(); return new BackupExternalDatabase( + UUID.randomUUID(), backup, database.getName(), database.getDatabaseRegistry().getFirst().getType(), @@ -202,7 +203,7 @@ private LogicalBackup createLogicalBackup(String adapterId, Map databaseRegistries = entry.getValue(); return new BackupDatabase( + UUID.randomUUID(), logicalBackup, DbaasBackupUtils.getDatabaseName(db), databaseRegistries.stream().map(DatabaseRegistry::getClassifier).toList(), @@ -689,7 +691,6 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, private RestoreResponse applyDryRunRestore(Backup backup, RestoreRequest restoreRequest) { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); - currRestore.getLogicalRestores().forEach(db -> db.setId(UUID.randomUUID())); // DryRun on adapters startRestore(currRestore, true); aggregateRestoreStatus(currRestore); @@ -778,6 +779,7 @@ protected Restore initializeFullRestoreStructure( List logicalRestores = groupedByTypeAndAdapter.entrySet().stream() .map(entry -> { LogicalRestore logicalRestore = new LogicalRestore(); + logicalRestore.setId(UUID.randomUUID()); logicalRestore.setType(entry.getValue().getFirst().backupDatabase().getLogicalBackup().getType()); logicalRestore.setAdapterId(entry.getKey().adapterId()); @@ -1000,6 +1002,7 @@ private List createRestoreDatabases( .toList(); RestoreDatabase restoreDatabase = new RestoreDatabase(); + restoreDatabase.setId(UUID.randomUUID()); restoreDatabase.setBackupDatabase(backupDatabase); restoreDatabase.setName(backupDatabase.getName()); restoreDatabase.setClassifiers(classifiers); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java index 9788eadb..6fd22a4a 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java @@ -23,10 +23,7 @@ import org.mockito.Mockito; import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.*; import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.Response.Status.*; @@ -399,6 +396,7 @@ private BackupResponse createBackupResponse(String backupName) { sortedMap.put("key", "value"); BackupDatabaseResponse backupDatabaseResponse = new BackupDatabaseResponse( + UUID.randomUUID(), "backup-database", List.of(sortedMap), Map.of("settings-key", "settings-value"), @@ -416,6 +414,7 @@ private BackupResponse createBackupResponse(String backupName) { ); LogicalBackupResponse logicalBackupResponse = new LogicalBackupResponse( + UUID.randomUUID(), "logicalBackupName", "adapterID", "type", diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index b431d537..b41c376d 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -3604,6 +3604,7 @@ private Classifier getClassifier(ClassifierType classifierType, String namespace private BackupExternalDatabase getBackupExternalDatabase(String name, String type, List> classifiers) { BackupExternalDatabase externalDatabase = new BackupExternalDatabase(); + externalDatabase.setId(UUID.randomUUID()); externalDatabase.setName(name); externalDatabase.setType(type); externalDatabase.setClassifiers(classifiers); @@ -3616,6 +3617,7 @@ private BackupDatabase getBackupDatabase(String dbName, BackupTaskStatus status, String errorMessage) { BackupDatabase backupDatabase = new BackupDatabase(); + backupDatabase.setId(UUID.randomUUID()); backupDatabase.setName(dbName); backupDatabase.setClassifiers(classifiers); backupDatabase.setSettings(Map.of("setting", "setting")); @@ -3637,6 +3639,7 @@ private LogicalBackup getLogicalBackup(String logicalBackupName, String errorMsg ) { LogicalBackup logicalBackup = new LogicalBackup(); + logicalBackup.setId(UUID.randomUUID()); logicalBackup.setLogicalBackupName(logicalBackupName); logicalBackup.setAdapterId(adapterId); logicalBackup.setType(type); @@ -3688,6 +3691,7 @@ private Backup getBackup(String backupName, String namespace) { List backupDatabases = new ArrayList<>(); for (int i = 0; i < 3; i++) { BackupDatabase backupDatabase = new BackupDatabase(); + backupDatabase.setId(UUID.randomUUID()); backupDatabase.setName("db" + i); backupDatabase.setUsers(List.of(new BackupDatabase.User("username", "role"))); backupDatabase.setStatus(BackupTaskStatus.COMPLETED); @@ -3699,6 +3703,7 @@ private Backup getBackup(String backupName, String namespace) { List logicalBackups = new ArrayList<>(); for (int i = 0; i < 2; i++) { LogicalBackup logicalBackup = new LogicalBackup(); + logicalBackup.setId(UUID.randomUUID()); logicalBackup.setLogicalBackupName("logicalBackupName" + i); logicalBackup.setStatus(BackupTaskStatus.COMPLETED); logicalBackup.setAdapterId(Integer.toString(i)); @@ -3745,6 +3750,7 @@ private Restore getRestore(String restoreName, String namespace) { List restoreDatabases = new ArrayList<>(); for (int i = 0; i < 3; i++) { RestoreDatabase restoreDatabase = new RestoreDatabase(); + restoreDatabase.setId(UUID.randomUUID()); restoreDatabase.setName("db" + i); restoreDatabase.setUsers(List.of(new RestoreDatabase.User("username", "admin"))); restoreDatabase.setStatus(RestoreTaskStatus.COMPLETED); @@ -3756,6 +3762,7 @@ private Restore getRestore(String restoreName, String namespace) { List logicalRestores = new ArrayList<>(); for (int i = 0; i < 2; i++) { LogicalRestore logicalRestore = new LogicalRestore(); + logicalRestore.setId(UUID.randomUUID()); logicalRestore.setLogicalRestoreName("logicalRestoreName" + i); logicalRestore.setStatus(RestoreTaskStatus.COMPLETED); logicalRestore.setAdapterId(Integer.toString(i)); @@ -3835,6 +3842,7 @@ private RestoreDatabase getRestoreDatabase(BackupDatabase backupDatabase, long duration, String errorMessage) { RestoreDatabase db = new RestoreDatabase(); + db.setId(UUID.randomUUID()); db.setBackupDatabase(backupDatabase); db.setName(dbName); db.setClassifiers(classifiers); @@ -3854,6 +3862,7 @@ private LogicalRestore getLogicalRestore(String logicalRestoreName, RestoreTaskStatus status, String errorMsg) { LogicalRestore logicalRestore = new LogicalRestore(); + logicalRestore.setId(UUID.randomUUID()); logicalRestore.setLogicalRestoreName(logicalRestoreName); logicalRestore.setAdapterId(adapterId); logicalRestore.setType(type); @@ -3899,6 +3908,7 @@ private RestoreExternalDatabase getRestoreExternalDb( List classifiers ) { RestoreExternalDatabase db = new RestoreExternalDatabase(); + db.setId(UUID.randomUUID()); db.setName(name); db.setType(type); db.setClassifiers(classifiers); @@ -3959,6 +3969,7 @@ private BackupResponse getBackupResponse(String backupName, String namespace) { sortedMap.put("key-second", Map.of("inner-key", "inner-value")); BackupDatabaseResponse backupDatabaseResponse = new BackupDatabaseResponse( + UUID.randomUUID(), "backup-database", List.of(sortedMap), Map.of("settings-key", "settings-value"), @@ -3976,6 +3987,7 @@ private BackupResponse getBackupResponse(String backupName, String namespace) { ); LogicalBackupResponse logicalBackupResponse = new LogicalBackupResponse( + UUID.randomUUID(), "logicalBackupName", "adapterID", "type", @@ -3996,6 +4008,7 @@ private BackupResponse getBackupResponse(String backupName, String namespace) { map.put("key", "value"); BackupExternalDatabaseResponse backupExternalDatabase = new BackupExternalDatabaseResponse(); + backupExternalDatabase.setId(UUID.randomUUID()); backupExternalDatabase.setName("Name"); backupExternalDatabase.setType("postgresql"); backupExternalDatabase.setClassifiers(List.of(map)); diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index ac93484f..ef0e7637 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -140,12 +140,21 @@ "BackupDatabaseResponse": { "type": "object", "required": [ + "id", "name", "status", "path" ], "description": "Logical database backup details", "properties": { + "id": { + "$ref": "#/components/schemas/UUID", + "type": "string", + "examples": [ + "550e8400-e29b-41d4-a716-446655440000" + ], + "description": "Identifier of the database" + }, "name": { "type": "string", "examples": [ @@ -258,10 +267,19 @@ "BackupExternalDatabaseResponse": { "type": "object", "required": [ + "id", "name" ], "description": "External database details", "properties": { + "id": { + "$ref": "#/components/schemas/UUID", + "type": "string", + "examples": [ + "550e8400-e29b-41d4-a716-446655440000" + ], + "description": "Identifier of the external database" + }, "name": { "type": "string", "examples": [ @@ -1989,6 +2007,7 @@ "LogicalBackupResponse": { "type": "object", "required": [ + "id", "logicalBackupName", "adapterId", "type", @@ -1997,6 +2016,14 @@ ], "description": "Logical backup details", "properties": { + "id": { + "$ref": "#/components/schemas/UUID", + "type": "string", + "examples": [ + "550e8400-e29b-41d4-a716-446655440000" + ], + "description": "Identifier of the logical backup" + }, "logicalBackupName": { "type": "string", "description": "Name of the logical backup in adapter" @@ -2060,6 +2087,7 @@ "LogicalRestoreResponse": { "type": "object", "required": [ + "id", "logicalRestoreName", "adapterId", "type", @@ -2068,6 +2096,14 @@ ], "description": "Logical restore details", "properties": { + "id": { + "$ref": "#/components/schemas/UUID", + "type": "string", + "examples": [ + "550e8400-e29b-41d4-a716-446655440000" + ], + "description": "Identifier of the logical restore" + }, "logicalRestoreName": { "type": "string", "description": "Name of the logical restore in adapter" @@ -3081,12 +3117,21 @@ "RestoreDatabaseResponse": { "type": "object", "required": [ + "id", "name", "status", "path" ], "description": "Logical database restore details", "properties": { + "id": { + "$ref": "#/components/schemas/UUID", + "type": "string", + "examples": [ + "550e8400-e29b-41d4-a716-446655440000" + ], + "description": "Identifier of the database" + }, "name": { "type": "string", "examples": [ @@ -3185,10 +3230,19 @@ "RestoreExternalDatabaseResponse": { "type": "object", "required": [ + "id", "name" ], "description": "External database details", "properties": { + "id": { + "$ref": "#/components/schemas/UUID", + "type": "string", + "examples": [ + "550e8400-e29b-41d4-a716-446655440000" + ], + "description": "Identifier of the database" + }, "name": { "type": "string", "examples": [ From 4a4b0abab30dd79edb19610d538d920588ba4fc8 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Mon, 2 Feb 2026 18:33:13 +0500 Subject: [PATCH 39/46] feat: rewrite retryRestore --- .../dbaas/entity/pg/backupV2/Restore.java | 4 + .../cloud/dbaas/enums/BackupTaskStatus.java | 2 +- .../cloud/dbaas/enums/RestoreTaskStatus.java | 2 +- .../dbaas/service/DbBackupV2Service.java | 97 ++++++++++--------- .../dbaas/service/DbBackupV2ServiceTest.java | 64 +++--------- 5 files changed, 73 insertions(+), 96 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java index 6a99de64..09e7d5f0 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java @@ -87,6 +87,10 @@ public void incrementAttempt() { this.attemptCount++; } + public void resetAttempt() { + this.attemptCount = 0; + } + @Override public boolean equals(Object o) { if (!(o instanceof Restore restore)) return false; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java index f0e51c99..f6398cf3 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java @@ -1,5 +1,5 @@ package com.netcracker.cloud.dbaas.enums; public enum BackupTaskStatus { - NOT_STARTED, IN_PROGRESS, FAILED, COMPLETED + NOT_STARTED, IN_PROGRESS, FAILED, FAILED_WITH_RETRY, COMPLETED } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java index 570976d2..bf3564cf 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java @@ -1,5 +1,5 @@ package com.netcracker.cloud.dbaas.enums; public enum RestoreTaskStatus { - NOT_STARTED, IN_PROGRESS, FAILED, COMPLETED + NOT_STARTED, IN_PROGRESS, FAILED, FAILED_WITH_RETRY, COMPLETED } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index f37b6007..c554d2ed 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -242,7 +242,7 @@ protected void startBackup(Backup backup) { refreshLogicalBackupState(logicalBackup, response) ) .exceptionally(throwable -> { - logicalBackup.setStatus(BackupTaskStatus.FAILED); + logicalBackup.setStatus(BackupTaskStatus.FAILED_WITH_RETRY); logicalBackup.setErrorMessage(extractErrorMessage(throwable)); return null; })) @@ -314,7 +314,7 @@ public void checkBackupsAsync() { backupsToAggregate.forEach(this::trackAndAggregate); } - protected void trackAndAggregate(Backup backup) { + protected void trackAndAggregate(Backup backup) { if (backup.getAttemptCount() > retryCount) { log.warn("The number of attempts to track backup {} exceeded {}", backup.getName(), retryCount); backup.setStatus(BackupStatus.FAILED); @@ -329,9 +329,11 @@ protected void trackAndAggregate(Backup backup) { private void fetchAndUpdateStatuses(Backup backup) { List notFinishedBackups = backup.getLogicalBackups().stream() - .filter(db -> db.getStatus() == BackupTaskStatus.IN_PROGRESS - || db.getStatus() == BackupTaskStatus.NOT_STARTED) - .toList(); + .filter(db -> + db.getStatus() == BackupTaskStatus.IN_PROGRESS + || db.getStatus() == BackupTaskStatus.NOT_STARTED + || db.getStatus() == BackupTaskStatus.FAILED_WITH_RETRY + ).toList(); List> futures = notFinishedBackups.stream() .map(this::trackLogicalBackupAsync) @@ -351,14 +353,20 @@ private CompletableFuture trackLogicalBackupAsync(LogicalBackup logicalBac ) .thenAccept(response -> refreshLogicalBackupState(logicalBackup, response)) .exceptionally(throwable -> { + logicalBackup.setStatus(BackupTaskStatus.FAILED_WITH_RETRY); logicalBackup.setErrorMessage(extractErrorMessage(throwable)); - logicalBackup.setStatus(BackupTaskStatus.FAILED); return null; }); } private LogicalBackupAdapterResponse executeTrackBackup(LogicalBackup logicalBackup) { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalBackup.getAdapterId()); + + if (logicalBackup.getLogicalBackupName() == null || logicalBackup.getLogicalBackupName().isEmpty()) { + LogicalBackupAdapterResponse response = startLogicalBackup(logicalBackup); + refreshLogicalBackupState(logicalBackup, response); + } + LogicalBackupAdapterResponse response = adapter.trackBackupV2( logicalBackup.getLogicalBackupName(), logicalBackup.getBackup().getStorageName(), @@ -1087,7 +1095,7 @@ protected void startRestore(Restore restore, boolean dryRun) { List> futures = logicalRestores.stream() .map(logicalRestore -> - runLogicalRestoreAsync(logicalRestore, logicalRestore.getRestoreDatabases(), storageName, blobPath, dryRun) + runLogicalRestoreAsync(logicalRestore, storageName, blobPath, dryRun) ) .toList(); @@ -1095,7 +1103,6 @@ protected void startRestore(Restore restore, boolean dryRun) { } private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRestore, - List restoreDatabases, String storageName, String blobPath, boolean dryRun @@ -1104,7 +1111,6 @@ private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRes asyncOperations.wrapWithContext( () -> logicalRestore( logicalRestore, - restoreDatabases, storageName, blobPath, dryRun @@ -1115,7 +1121,7 @@ private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRes .thenAccept(response -> refreshLogicalRestoreState(logicalRestore, response)) .exceptionally(throwable -> { - logicalRestore.setStatus(RestoreTaskStatus.FAILED); + logicalRestore.setStatus(RestoreTaskStatus.FAILED_WITH_RETRY); logicalRestore.setErrorMessage(extractErrorMessage(throwable)); log.error("Logical restore failed: adapterId={}, error={}", logicalRestore.getAdapterId(), logicalRestore.getErrorMessage()); @@ -1168,12 +1174,13 @@ private void refreshLogicalRestoreState(LogicalRestore logicalRestore, LogicalRe } private LogicalRestoreAdapterResponse logicalRestore(LogicalRestore logicalRestore, - List restoreDatabases, String storageName, String blobPath, boolean dryRun ) { - String logicalBackupName = restoreDatabases.getFirst() + List restoreDatabases = logicalRestore.getRestoreDatabases(); + String logicalBackupName = restoreDatabases + .getFirst() .getBackupDatabase() .getLogicalBackup() .getLogicalBackupName(); @@ -1320,26 +1327,36 @@ protected void trackAndAggregateRestore(Restore restore) { private void fetchStatuses(Restore restore) { List notFinishedLogicalRestores = restore.getLogicalRestores().stream() - .filter(db -> RestoreTaskStatus.IN_PROGRESS == db.getStatus() - || RestoreTaskStatus.NOT_STARTED == db.getStatus()) - .toList(); + .filter(db -> + db.getStatus() == RestoreTaskStatus.IN_PROGRESS + || db.getStatus() == RestoreTaskStatus.NOT_STARTED + || db.getStatus() == RestoreTaskStatus.FAILED_WITH_RETRY + ).toList(); log.debug("Starting checking status for logical restores: restore={}, logicalRestores={}", restore.getName(), notFinishedLogicalRestores.stream() - .map(LogicalRestore::getLogicalRestoreName) + .map(LogicalRestore::getId) .toList()); List> futures = notFinishedLogicalRestores.stream() .map(logicalRestore -> { RetryPolicy retryPolicy = buildRetryPolicy(TRACK_RESTORE_OPERATION, LOGICAL_RESTORE, logicalRestore.getId().toString(), logicalRestore.getAdapterId()); return CompletableFuture.supplyAsync( - asyncOperations.wrapWithContext(() -> Failsafe.with(retryPolicy).get(() -> { - DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalRestore.getAdapterId()); - return adapter.trackRestoreV2(logicalRestore.getLogicalRestoreName(), restore.getStorageName(), restore.getBlobPath()); - }))) + asyncOperations.wrapWithContext( + () -> Failsafe.with(retryPolicy).get(() -> { + DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalRestore.getAdapterId()); + if (logicalRestore.getLogicalRestoreName() == null || logicalRestore.getLogicalRestoreName().isBlank()) { + LogicalRestoreAdapterResponse response = logicalRestore(logicalRestore, restore.getStorageName(), restore.getBlobPath(), false); + refreshLogicalRestoreState(logicalRestore, response); + } + return adapter.trackRestoreV2(logicalRestore.getLogicalRestoreName(), restore.getStorageName(), restore.getBlobPath()); + } + ) + ), asyncOperations.getBackupPool()) .thenAccept(response -> refreshLogicalRestoreState(logicalRestore, response)) .exceptionally(throwable -> { + logicalRestore.setStatus(RestoreTaskStatus.FAILED_WITH_RETRY); logicalRestore.setErrorMessage(throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage()); return null; @@ -1587,8 +1604,7 @@ public RestoreResponse retryRestore(String restoreName) { Restore retriedRestore = restoreLockWrapper(() -> { retryRestore(restore); aggregateRestoreStatus(restore); - restoreRepository.save(restore); - return restore; + return restoreRepository.save(restore); }); return mapper.toRestoreResponse(retriedRestore); } @@ -1603,27 +1619,18 @@ private void checkBackupStatusForRestore(String restoreName, BackupStatus status } private void retryRestore(Restore restore) { - List failedLogicalRestores = restore.getLogicalRestores() - .stream() - .filter(logicalRestore -> RestoreTaskStatus.FAILED == logicalRestore.getStatus()) - .toList(); - - log.info("Starting retry restore process: restore={}, failedLogicalRestoreCount={}", - restore.getName(), failedLogicalRestores.size()); - String storageName = restore.getStorageName(); - String blobPath = restore.getBlobPath(); - - List> futures = failedLogicalRestores.stream() - .map(logicalRestore -> { - List failedRestoreDatabases = logicalRestore.getRestoreDatabases().stream() - .filter(db -> RestoreTaskStatus.FAILED == db.getStatus()) - .toList(); - return runLogicalRestoreAsync(logicalRestore, failedRestoreDatabases, storageName, blobPath, false); - } + restore.resetAttempt(); + restore.setStatus(RestoreStatus.IN_PROGRESS); + restore.getLogicalRestores().stream() + .filter(logicalRestore -> + RestoreTaskStatus.FAILED == logicalRestore.getStatus() + || logicalRestore.getLogicalRestoreName() == null + || logicalRestore.getLogicalRestoreName().isEmpty() ) - .toList(); - - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + .forEach(logicalRestore -> { + logicalRestore.setStatus(RestoreTaskStatus.FAILED_WITH_RETRY); + logicalRestore.setLogicalRestoreName(null); + }); } protected Map> validateAndFilterDatabasesForBackup( @@ -1791,12 +1798,12 @@ private static , R extends Enum> R aggregateStatus( Function taskStatusGetter, Function resultStatusGetter) { - if (statusSet.contains(taskStatusGetter.apply("NOT_STARTED")) && statusSet.size() == 1) - return resultStatusGetter.apply("NOT_STARTED"); - if (statusSet.contains(taskStatusGetter.apply("NOT_STARTED")) && statusSet.size() > 1) return resultStatusGetter.apply("IN_PROGRESS"); + if (statusSet.contains(taskStatusGetter.apply("FAILED_WITH_RETRY"))) + return resultStatusGetter.apply("IN_PROGRESS"); + if (statusSet.contains(taskStatusGetter.apply("IN_PROGRESS"))) return resultStatusGetter.apply("IN_PROGRESS"); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index b41c376d..f7bcf980 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -1977,7 +1977,7 @@ void retryRestore() { DbaasAdapter dbaasAdapter = Mockito.mock(DbaasAdapter.class); when(physicalDatabasesService.getAdapterById(adapterId)).thenReturn(dbaasAdapter); - when(dbaasAdapter.isBackupRestoreSupported()).thenReturn(true); +// when(dbaasAdapter.isBackupRestoreSupported()).thenReturn(true); // Mock logic of choosing adapter in new/current env ExternalAdapterRegistrationEntry adapter1 = new ExternalAdapterRegistrationEntry(); @@ -1986,17 +1986,17 @@ void retryRestore() { physicalDatabase1.setAdapter(adapter1); physicalDatabase1.setType(postgresType); physicalDatabase1.setPhysicalDatabaseIdentifier("postgres-dev"); - - when(balancingRulesService.applyBalancingRules(postgresType, mappedNamespace, microserviceName1)) - .thenReturn(physicalDatabase1); +// +// when(balancingRulesService.applyBalancingRules(postgresType, mappedNamespace, microserviceName1)) +// .thenReturn(physicalDatabase1); when(physicalDatabasesService.getByAdapterId(adapterId)).thenReturn(physicalDatabase1); - // Response during the sync restore process + // Response during the async restore process LogicalRestoreAdapterResponse response = LogicalRestoreAdapterResponse.builder() - .status(IN_PROGRESS_STATUS) + .status(COMPLETED_STATUS) .restoreId(logicalRestoreName) .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() - .status(IN_PROGRESS_STATUS) + .status(COMPLETED_STATUS) .previousDatabaseName(dbName) .databaseName(newName) .duration(1) @@ -2005,24 +2005,10 @@ void retryRestore() { // Same answer to DryRun non-DryRun mode when(dbaasAdapter.restoreV2(eq(logicalBackupName), anyBoolean(), any())) - .thenReturn(response) .thenReturn(response); - // Response during the async restore process - LogicalRestoreAdapterResponse response2 = LogicalRestoreAdapterResponse.builder() - .status(COMPLETED_STATUS) - .restoreId(logicalRestoreName) - .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() - .status(COMPLETED_STATUS) - .previousDatabaseName(dbName) - .databaseName(newName) - .duration(1) - .build())) - .build(); - when(dbaasAdapter.trackRestoreV2(eq(logicalRestoreName), any(), any())) - .thenReturn(response2); - + .thenReturn(response); // Mocks to ensure user process DbResource resource1 = new DbResource(); resource1.setId(UUID.randomUUID()); @@ -2059,30 +2045,10 @@ void retryRestore() { assertEquals(1, restoreResponse.getLogicalRestores().size()); LogicalRestoreResponse logicalRestoreResponse = restoreResponse.getLogicalRestores().getFirst(); - assertEquals(logicalRestoreName, logicalRestoreResponse.getLogicalRestoreName()); + assertNull(logicalRestoreResponse.getLogicalRestoreName()); assertEquals(adapterId, logicalRestoreResponse.getAdapterId()); assertEquals(2, logicalRestoreResponse.getRestoreDatabases().size()); - RestoreDatabaseResponse restoreDatabaseResponse1 = logicalRestoreResponse.getRestoreDatabases().stream() - .filter(db -> newName.equals(db.getName())).findAny().orElse(null); - assertEquals(1, restoreDatabaseResponse1.getClassifiers().size()); - - ClassifierResponse classifierResponse1 = restoreDatabaseResponse1.getClassifiers().getFirst(); - assertEquals(classifierWrapper1.getType(), classifierResponse1.getType()); - assertEquals(classifierWrapper1.getClassifier(), classifierResponse1.getClassifier()); - assertEquals(classifierWrapper1.getClassifierBeforeMapper(), classifierResponse1.getClassifierBeforeMapper()); - assertEquals(RestoreTaskStatus.IN_PROGRESS, restoreDatabaseResponse1.getStatus()); - - RestoreDatabaseResponse restoreDatabaseResponse2 = logicalRestoreResponse.getRestoreDatabases().stream() - .filter(db -> newName2.equals(db.getName())).findAny().orElse(null); - assertEquals(1, restoreDatabaseResponse2.getClassifiers().size()); - - ClassifierResponse classifierResponse2 = restoreDatabaseResponse2.getClassifiers().getFirst(); - assertEquals(classifierWrapper2.getType(), classifierResponse2.getType()); - assertEquals(classifierWrapper2.getClassifier(), classifierResponse2.getClassifier()); - assertEquals(classifierWrapper2.getClassifierBeforeMapper(), classifierResponse2.getClassifierBeforeMapper()); - assertEquals(RestoreTaskStatus.COMPLETED, restoreDatabaseResponse2.getStatus()); - // Start assert initialized dbs List databaseRegistries = databaseRegistryDbaasRepository.findAllDatabaseRegistersAnyLogType(); assertEquals(3, databaseRegistries.size()); @@ -3670,13 +3636,13 @@ private Backup getBackup(String name, backup.setStatus(status); backup.setTotal(logicalBackups.stream().mapToInt(db -> db.getBackupDatabases().size()).sum()); backup.setCompleted((int) logicalBackups.stream() - .flatMap(db -> db.getBackupDatabases().stream()) - .filter(bd -> BackupTaskStatus.COMPLETED == bd.getStatus()) - .count()); + .flatMap(db -> db.getBackupDatabases().stream()) + .filter(bd -> BackupTaskStatus.COMPLETED == bd.getStatus()) + .count()); backup.setSize(logicalBackups.stream() - .flatMap(db -> db.getBackupDatabases().stream()) - .mapToLong(BackupDatabase::getSize) - .sum()); + .flatMap(db -> db.getBackupDatabases().stream()) + .mapToLong(BackupDatabase::getSize) + .sum()); backup.setErrorMessage(errorMsg); logicalBackups.forEach(db -> db.setBackup(backup)); From 157b3a04ce06471e1ff6e451d13bd3742d9c134c Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 5 Feb 2026 11:13:21 +0500 Subject: [PATCH 40/46] style: detailed OpenApi --- .../v3/DatabaseBackupV2Controller.java | 18 ++++++++++++++ .../dto/backupV2/BackupDatabaseResponse.java | 2 +- .../BackupExternalDatabaseResponse.java | 2 +- .../dbaas/dto/backupV2/BackupRequest.java | 2 +- .../dbaas/dto/backupV2/FilterCriteria.java | 9 +++---- .../dto/backupV2/RestoreDatabaseResponse.java | 2 +- .../RestoreExternalDatabaseResponse.java | 2 +- .../cloud/dbaas/enums/BackupTaskStatus.java | 2 +- .../cloud/dbaas/enums/RestoreTaskStatus.java | 2 +- .../cloud/dbaas/exceptions/ErrorCodes.java | 2 +- .../OperationAlreadyRunningException.java | 8 +++---- docs/OpenAPI.json | 24 +++++++++++-------- 12 files changed, 49 insertions(+), 26 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index 37a856af..72ddaf55 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -338,4 +338,22 @@ public Response retryRestore(@Parameter(description = "Unique name of the restor @NotBlank String restoreName) { return Response.accepted(dbBackupV2Service.retryRestore(restoreName)).build(); } + + @Operation(summary = "Remove backup", + description = "Deleting a backup by the specified backup name", + hidden = true + ) + @APIResponses({ + @APIResponse(responseCode = "204", description = "The backup operation deleted successfully", content = @Content(schema = @Schema(implementation = String.class))), + @APIResponse(responseCode = "403", description = "The DBaaS is working in PROD mode. Deleting backup is prohibited", content = @Content(schema = @Schema(implementation = String.class))) + }) + @Path("/backup/{backupName}/forceDelete") + @DELETE + public Response deleteBackup(@Parameter(description = "Unique name of the backup operation", required = true) + @PathParam("backupName") + @NotBlank String backupName + ) { + dbBackupV2Service.deleteBackup(backupName); + return Response.noContent().build(); + } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java index 2da0aee3..5c2eed6b 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupDatabaseResponse.java @@ -18,7 +18,7 @@ @Schema(description = "Logical database backup details") public class BackupDatabaseResponse { @Schema( - description = "Identifier of the database", + description = "Identifier of the backup database", examples = {"550e8400-e29b-41d4-a716-446655440000"}, required = true ) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java index d7924ec1..b08d5ef9 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupExternalDatabaseResponse.java @@ -13,7 +13,7 @@ @Schema(description = "External database details") public class BackupExternalDatabaseResponse { @Schema( - description = "Identifier of the external database", + description = "Identifier of the external backup database", examples = {"550e8400-e29b-41d4-a716-446655440000"}, required = true ) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java index 340d3097..df21a328 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/BackupRequest.java @@ -58,7 +58,7 @@ public class BackupRequest { private ExternalDatabaseStrategy externalDatabaseStrategy = ExternalDatabaseStrategy.FAIL; @NotNull @Schema( - description = "Whether non‑backupable databases were ignored during backup", + description = "Whether non-backupable databases should be ignored during backup", examples = { "false" }, diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java index 72c03edb..d7004712 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java @@ -15,19 +15,20 @@ @NoArgsConstructor @Schema(description = "Group of filters for backup and restore operations. Filters are applied in the following order:\n" + "\n" + - "1. `filter`: Apply the filter to the databases.\n" + - "2. `exclude`: Exclude databases that match any of the filters in the list.") + "1. `filter`\n" + + "2. `exclude`") public class FilterCriteria { @Schema( - description = "Apply the filter to the remaining databases" + description = "Include databases that match any of the filters in the list" ) @NotNull(groups = {BackupGroup.class}) - @Size(min = 1, groups = {BackupGroup.class}) + @Size(min = 1, groups = {BackupGroup.class}, message = "there should be at least one filter specified") @Valid private List filter = new ArrayList<>(); @Schema( description = "Exclude databases that match any of the filters in the list" ) + @Valid private List exclude = new ArrayList<>(); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java index bb9705fa..97f83ee3 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java @@ -16,7 +16,7 @@ @Schema(description = "Logical database restore details") public class RestoreDatabaseResponse { @Schema( - description = "Identifier of the database", + description = "Identifier of the restore database", examples = {"550e8400-e29b-41d4-a716-446655440000"}, required = true ) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index 1b54f817..8b1e0a51 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -13,7 +13,7 @@ @Schema(description = "External database details") public class RestoreExternalDatabaseResponse { @Schema( - description = "Identifier of the database", + description = "Identifier of the external restore database", examples = {"550e8400-e29b-41d4-a716-446655440000"}, required = true ) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java index f6398cf3..0f0279cc 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/BackupTaskStatus.java @@ -1,5 +1,5 @@ package com.netcracker.cloud.dbaas.enums; public enum BackupTaskStatus { - NOT_STARTED, IN_PROGRESS, FAILED, FAILED_WITH_RETRY, COMPLETED + NOT_STARTED, IN_PROGRESS, FAILED, RETRYABLE_FAIL, COMPLETED } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java index bf3564cf..131459db 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/enums/RestoreTaskStatus.java @@ -1,5 +1,5 @@ package com.netcracker.cloud.dbaas.enums; public enum RestoreTaskStatus { - NOT_STARTED, IN_PROGRESS, FAILED, FAILED_WITH_RETRY, COMPLETED + NOT_STARTED, IN_PROGRESS, FAILED, RETRYABLE_FAIL, COMPLETED } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java index 1fe052d2..2a536a27 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/ErrorCodes.java @@ -214,7 +214,7 @@ public enum ErrorCodes implements ErrorCode { "Resource with name '%s' already exists"), CORE_DBAAS_4047( "CORE-DBAAS-4047", - "Backup not allowed", + "Operation not allowed", "The backup/restore request can't be processed. %s" ), CORE_DBAAS_4048( diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java index 7ee656b2..d22fb78c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/exceptions/OperationAlreadyRunningException.java @@ -1,9 +1,9 @@ package com.netcracker.cloud.dbaas.exceptions; -import com.netcracker.cloud.dbaas.dto.Source; +import com.netcracker.cloud.core.error.runtime.ErrorCodeException; -public class OperationAlreadyRunningException extends ValidationException { - public OperationAlreadyRunningException(String operation, Source source) { - super(ErrorCodes.CORE_DBAAS_4053, ErrorCodes.CORE_DBAAS_4053.getDetail(operation), source); +public class OperationAlreadyRunningException extends ErrorCodeException { + public OperationAlreadyRunningException(String operation) { + super(ErrorCodes.CORE_DBAAS_4053, ErrorCodes.CORE_DBAAS_4053.getDetail(operation)); } } diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index ef0e7637..a7832d4f 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -153,7 +153,7 @@ "examples": [ "550e8400-e29b-41d4-a716-446655440000" ], - "description": "Identifier of the database" + "description": "Identifier of the backup database" }, "name": { "type": "string", @@ -220,6 +220,7 @@ "NOT_STARTED", "IN_PROGRESS", "FAILED", + "RETRYABLE_FAIL", "COMPLETED" ], "description": "Current state of the backup database" @@ -278,7 +279,7 @@ "examples": [ "550e8400-e29b-41d4-a716-446655440000" ], - "description": "Identifier of the external database" + "description": "Identifier of the external backup database" }, "name": { "type": "string", @@ -358,7 +359,7 @@ "items": { "$ref": "#/components/schemas/Filter" }, - "description": "Apply the filter to the remaining databases" + "description": "Include databases that match any of the filters in the list" }, "exclude": { "type": "array", @@ -390,7 +391,7 @@ "examples": [ false ], - "description": "Whether non‑backupable databases were ignored during backup", + "description": "Whether non-backupable databases should be ignored during backup", "default": false } } @@ -465,7 +466,7 @@ "items": { "$ref": "#/components/schemas/Filter" }, - "description": "Apply the filter to the remaining databases" + "description": "Include databases that match any of the filters in the list" }, "exclude": { "type": "array", @@ -1832,7 +1833,7 @@ "items": { "$ref": "#/components/schemas/Filter" }, - "description": "Apply the filter to the remaining databases" + "description": "Include databases that match any of the filters in the list" }, "exclude": { "type": "array", @@ -2048,6 +2049,7 @@ "NOT_STARTED", "IN_PROGRESS", "FAILED", + "RETRYABLE_FAIL", "COMPLETED" ], "description": "Current state of the backup databases of one adapter" @@ -2138,6 +2140,7 @@ "NOT_STARTED", "IN_PROGRESS", "FAILED", + "RETRYABLE_FAIL", "COMPLETED" ], "description": "Current state of the restore operation" @@ -3130,7 +3133,7 @@ "examples": [ "550e8400-e29b-41d4-a716-446655440000" ], - "description": "Identifier of the database" + "description": "Identifier of the restore database" }, "name": { "type": "string", @@ -3191,6 +3194,7 @@ "NOT_STARTED", "IN_PROGRESS", "FAILED", + "RETRYABLE_FAIL", "COMPLETED" ], "description": "Current state of the restore database" @@ -3241,7 +3245,7 @@ "examples": [ "550e8400-e29b-41d4-a716-446655440000" ], - "description": "Identifier of the database" + "description": "Identifier of the external restore database" }, "name": { "type": "string", @@ -3332,7 +3336,7 @@ "items": { "$ref": "#/components/schemas/Filter" }, - "description": "Apply the filter to the remaining databases" + "description": "Include databases that match any of the filters in the list" }, "exclude": { "type": "array", @@ -3459,7 +3463,7 @@ "items": { "$ref": "#/components/schemas/Filter" }, - "description": "Apply the filter to the remaining databases" + "description": "Include databases that match any of the filters in the list" }, "exclude": { "type": "array", From 0b72355d74283efe0799c9fc3c4425362c37c320 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 5 Feb 2026 13:28:35 +0500 Subject: [PATCH 41/46] chore: rename entities and dtos --- .../backupV2/ClassifierDetailsResponse.java | 44 ++ .../dto/backupV2/ClassifierResponse.java | 18 - .../dto/backupV2/RestoreDatabaseResponse.java | 2 +- .../RestoreExternalDatabaseResponse.java | 4 +- .../dto/backupV2/BackupDatabaseDelegate.java | 10 - .../dto/backupV2/BackupWithClassifiers.java | 10 + ...Classifier.java => ClassifierDetails.java} | 4 +- .../dbaas/entity/pg/backupV2/Restore.java | 2 - .../entity/pg/backupV2/RestoreDatabase.java | 2 +- .../pg/backupV2/RestoreExternalDatabase.java | 2 +- .../cloud/dbaas/mapper/BackupV2Mapper.java | 6 +- .../repositories/pg/jpa/BackupRepository.java | 2 +- .../pg/jpa/RestoreRepository.java | 2 +- .../dbaas/service/DbBackupV2Service.java | 350 ++++++++-------- .../v3/DatabaseBackupV2ControllerTest.java | 21 +- .../dbaas/service/DbBackupV2ServiceTest.java | 394 ++++-------------- 16 files changed, 333 insertions(+), 540 deletions(-) create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java delete mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java delete mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupWithClassifiers.java rename dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/{Classifier.java => ClassifierDetails.java} (89%) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java new file mode 100644 index 00000000..56946baa --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java @@ -0,0 +1,44 @@ +package com.netcracker.cloud.dbaas.dto.backupV2; + +import com.netcracker.cloud.dbaas.enums.ClassifierType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.util.SortedMap; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Classifier details used during restore operation") +public class ClassifierDetailsResponse { + @Schema( + description = "Type of classifier in restore context", + required = true, + implementation = ClassifierType.class + ) + private ClassifierType type; + @Schema( + description = "Name of the source database. From which the classifier was obtained via backup", + required = true, + examples = { + "oldDb" + } + ) + private String previousDatabase; + @Schema( + description = "Final classifier used to create a database in the target environment. ", + examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]", + required = true + ) + private SortedMap classifier; + @Schema( + description = "Classifier state before applying restore-side mapping. " + + "Represents classifier data coming from backup, adapted to restore model " + + "but not yet transformed", + examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]", + required = true + ) + private SortedMap classifierBeforeMapper; +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java deleted file mode 100644 index 9b031e77..00000000 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.netcracker.cloud.dbaas.dto.backupV2; - -import com.netcracker.cloud.dbaas.enums.ClassifierType; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.SortedMap; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class ClassifierResponse { - private ClassifierType type; - private String previousDatabase; - private SortedMap classifier; - private SortedMap classifierBeforeMapper; -} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java index 97f83ee3..6031b9f5 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreDatabaseResponse.java @@ -33,7 +33,7 @@ public class RestoreDatabaseResponse { description = "List of database classifiers. Each classifier is a sorted map of attributes.", examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]" ) - private List classifiers; + private List classifiers; @Schema( description = "List of database users", examples = "[{\"name\":\"username\",\"role\":\"admin\"}" diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java index 8b1e0a51..dc7519b6 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/RestoreExternalDatabaseResponse.java @@ -24,9 +24,9 @@ public class RestoreExternalDatabaseResponse { private String type; @Schema( description = "List of classifier objects describing database attributes.", - implementation = ClassifierResponse.class, + implementation = ClassifierDetailsResponse.class, type = SchemaType.ARRAY ) - private List classifiers; + private List classifiers; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java deleted file mode 100644 index 7bd56f81..00000000 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupDatabaseDelegate.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.netcracker.cloud.dbaas.entity.dto.backupV2; - -import com.netcracker.cloud.dbaas.entity.pg.backupV2.Classifier; -import com.netcracker.cloud.dbaas.entity.pg.backupV2.BackupDatabase; - -import java.util.List; - - -public record BackupDatabaseDelegate(BackupDatabase backupDatabase, - List classifiers) {} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupWithClassifiers.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupWithClassifiers.java new file mode 100644 index 00000000..30f738aa --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupWithClassifiers.java @@ -0,0 +1,10 @@ +package com.netcracker.cloud.dbaas.entity.dto.backupV2; + +import com.netcracker.cloud.dbaas.entity.pg.backupV2.ClassifierDetails; +import com.netcracker.cloud.dbaas.entity.pg.backupV2.BackupDatabase; + +import java.util.List; + + +public record BackupWithClassifiers(BackupDatabase backupDatabase, + List classifiers) {} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/ClassifierDetails.java similarity index 89% rename from dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java rename to dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/ClassifierDetails.java index 377814bb..02e16d17 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Classifier.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/ClassifierDetails.java @@ -11,7 +11,7 @@ @Data @NoArgsConstructor @AllArgsConstructor -public class Classifier { +public class ClassifierDetails { private ClassifierType type; private String previousDatabase; private SortedMap classifier; @@ -19,7 +19,7 @@ public class Classifier { @Override public boolean equals(Object o) { - if (!(o instanceof Classifier that)) return false; + if (!(o instanceof ClassifierDetails that)) return false; return type == that.type && Objects.equals(classifier, that.classifier) && Objects.equals(classifierBeforeMapper, that.classifierBeforeMapper); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java index 09e7d5f0..7985eb47 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/Restore.java @@ -67,8 +67,6 @@ public class Restore { private Integer completed; - private Long duration; - @Column(name = "error_message") private String errorMessage; diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java index ec257237..1e8856c6 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreDatabase.java @@ -37,7 +37,7 @@ public class RestoreDatabase { @NotNull @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") - private List classifiers; + private List classifiers; @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java index 6df00818..a9bd9e22 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/RestoreExternalDatabase.java @@ -36,5 +36,5 @@ public class RestoreExternalDatabase { @NotNull @JdbcTypeCode(SqlTypes.JSON) @Column(columnDefinition = "jsonb") - private List classifiers; + private List classifiers; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java index ec136767..3e169f84 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/mapper/BackupV2Mapper.java @@ -102,9 +102,9 @@ private static > R mapStatus( @Mapping(target = "name", source = "backupExternalDatabase.name") @Mapping(target = "type", source = "backupExternalDatabase.type") @Mapping(target = "classifiers", source = "classifiers") - RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDatabase backupExternalDatabase, List classifiers); + RestoreExternalDatabase toRestoreExternalDatabase(BackupExternalDatabase backupExternalDatabase, List classifiers); - ClassifierResponse toClassifierResponse(Classifier classifier); + ClassifierDetailsResponse toClassifierResponse(ClassifierDetails classifier); - List toClassifierResponse(List classifiers); + List toClassifierResponse(List classifiers); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java index ebe26b3e..61b6831d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/BackupRepository.java @@ -18,7 +18,7 @@ public Backup save(Backup backup) { return entityManager.merge(backup); } - public List findBackupsToAggregate() { + public List findBackupsToTrack() { return list("status in ?1", List.of(BackupStatus.IN_PROGRESS)); } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java index dd66cb03..87fada69 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/RestoreRepository.java @@ -18,7 +18,7 @@ public Restore save(Restore restore) { return entityManager.merge(restore); } - public List findRestoresToAggregate() { + public List findRestoresToTrack() { return list("status in ?1", List.of(RestoreStatus.IN_PROGRESS)); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index c554d2ed..aad11e5c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -40,9 +40,11 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.netcracker.cloud.dbaas.Constants.*; import static com.netcracker.cloud.dbaas.entity.shared.AbstractDbState.DatabaseStateStatus.CREATED; +import static com.netcracker.cloud.dbaas.enums.RestoreTaskStatus.NOT_STARTED; import static com.netcracker.cloud.dbaas.service.DBaaService.MARKED_FOR_DROP; import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP; @@ -75,6 +77,7 @@ public class DbBackupV2Service { private final PasswordEncryption encryption; private final BgNamespaceRepository bgNamespaceRepository; private final LockProvider lockProvider; + private final DbaaSHelper dbaaSHelper; private final Duration retryDelay; private final int retryAttempts; @@ -92,6 +95,7 @@ public DbBackupV2Service(BackupRepository backupRepository, PasswordEncryption encryption, BgNamespaceRepository bgNamespaceRepository, LockProvider lockProvider, + DbaaSHelper dbaaSHelper, @ConfigProperty(name = "dbaas.backup-restore.retry.delay.seconds") Duration retryDelay, @ConfigProperty(name = "dbaas.backup-restore.retry.attempts") int retryAttempts, @ConfigProperty(name = "dbaas.backup-restore.check.attempts") int retryCount @@ -107,6 +111,7 @@ public DbBackupV2Service(BackupRepository backupRepository, this.encryption = encryption; this.bgNamespaceRepository = bgNamespaceRepository; this.lockProvider = lockProvider; + this.dbaaSHelper = dbaaSHelper; this.retryDelay = retryDelay; this.retryAttempts = retryAttempts; this.retryCount = retryCount; @@ -242,7 +247,7 @@ protected void startBackup(Backup backup) { refreshLogicalBackupState(logicalBackup, response) ) .exceptionally(throwable -> { - logicalBackup.setStatus(BackupTaskStatus.FAILED_WITH_RETRY); + logicalBackup.setStatus(BackupTaskStatus.RETRYABLE_FAIL); logicalBackup.setErrorMessage(extractErrorMessage(throwable)); return null; })) @@ -310,7 +315,7 @@ public void checkBackupsAsync() { //TODO propagate correct business id log.info("Starting backup scheduler"); LockAssert.assertLocked(); - List backupsToAggregate = backupRepository.findBackupsToAggregate(); + List backupsToAggregate = backupRepository.findBackupsToTrack(); backupsToAggregate.forEach(this::trackAndAggregate); } @@ -332,7 +337,7 @@ private void fetchAndUpdateStatuses(Backup backup) { .filter(db -> db.getStatus() == BackupTaskStatus.IN_PROGRESS || db.getStatus() == BackupTaskStatus.NOT_STARTED - || db.getStatus() == BackupTaskStatus.FAILED_WITH_RETRY + || db.getStatus() == BackupTaskStatus.RETRYABLE_FAIL ).toList(); List> futures = notFinishedBackups.stream() @@ -353,7 +358,7 @@ private CompletableFuture trackLogicalBackupAsync(LogicalBackup logicalBac ) .thenAccept(response -> refreshLogicalBackupState(logicalBackup, response)) .exceptionally(throwable -> { - logicalBackup.setStatus(BackupTaskStatus.FAILED_WITH_RETRY); + logicalBackup.setStatus(BackupTaskStatus.RETRYABLE_FAIL); logicalBackup.setErrorMessage(extractErrorMessage(throwable)); return null; }); @@ -362,7 +367,7 @@ private CompletableFuture trackLogicalBackupAsync(LogicalBackup logicalBac private LogicalBackupAdapterResponse executeTrackBackup(LogicalBackup logicalBackup) { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalBackup.getAdapterId()); - if (logicalBackup.getLogicalBackupName() == null || logicalBackup.getLogicalBackupName().isEmpty()) { + if (isNameEmpty(logicalBackup.getLogicalBackupName())) { LogicalBackupAdapterResponse response = startLogicalBackup(logicalBackup); refreshLogicalBackupState(logicalBackup, response); } @@ -374,9 +379,9 @@ private LogicalBackupAdapterResponse executeTrackBackup(LogicalBackup logicalBac ); if (response == null) { - log.error("Empty response from {} for logicalBackup={}", TRACK_BACKUP_OPERATION, logicalBackup.getId()); + log.error("Operation {} return empty response from adapter={} for logicalBackup={}", TRACK_BACKUP_OPERATION, logicalBackup.getAdapterId(), logicalBackup.getId()); //not obvious from throw new BackupExecutionException( - String.format("Empty response from %s for logicalBackup=%s", TRACK_BACKUP_OPERATION, logicalBackup.getId()), + String.format("Operation %s return empty response from adapter=%s for logicalBackup=%s", TRACK_BACKUP_OPERATION, logicalBackup.getAdapterId(), logicalBackup.getId()), new Throwable() ); } @@ -415,38 +420,18 @@ protected void updateAggregatedStatus(Backup backup) { } } - backup.setStatus(aggregateBackupStatus(statusSet)); + backup.setStatus(aggregateBackupTaskStatus(statusSet)); backup.setSize(totalBytes); backup.setTotal(totalDbCount); backup.setCompleted(completedDbCount); backup.setErrorMessage(String.join("; ", errorMessages)); } - protected BackupStatus aggregateBackupStatus(Set statusSet) { - return aggregateStatus(statusSet, - BackupTaskStatus::valueOf, - BackupStatus::valueOf); - } - - protected RestoreStatus aggregateRestoreStatus(Set statusSet) { - return aggregateStatus(statusSet, - RestoreTaskStatus::valueOf, - RestoreStatus::valueOf); - } - public BackupStatusResponse getCurrentStatus(String backupName) { return mapper.toBackupStatusResponse(getBackupOrThrowException(backupName)); } protected Map> getAllDbByFilter(FilterCriteria filterCriteria) { - int uniqKinds = (int) filterCriteria.getExclude().stream() - .flatMap(exclude -> exclude.getDatabaseKind().stream()) - .distinct() - .count(); - if (uniqKinds == DatabaseKind.values().length) { - log.warn("No databases matching the filtering criteria were found during the backup"); - throw new DbNotFoundException("No databases matching the filtering criteria were found during the backup", Source.builder().build()); - } List filteredDatabases = databaseRegistryDbaasRepository .findAllDatabasesByFilter(filterCriteria.getFilter()) .stream() @@ -454,7 +439,6 @@ protected Map> getAllDbByFilter(FilterCriteria if (!isValidRegistry(registry)) return false; return filterCriteria.getExclude().stream() - .filter(this::isFilled) .noneMatch(exclude -> { boolean configurational = registry.getBgVersion() != null && !registry.getBgVersion().isBlank(); return isMatches(exclude, @@ -705,14 +689,14 @@ private RestoreResponse applyDryRunRestore(Backup backup, RestoreRequest restore return mapper.toRestoreResponse(currRestore); } - protected List getAllDbByFilter(List backupDatabasesToFilter, FilterCriteria filterCriteria) { + protected List getAllDbByFilter(List backupDatabasesToFilter, FilterCriteria filterCriteria) { if (isFilterEmpty(filterCriteria)) return backupDatabasesToFilter.stream() .map(db -> - new BackupDatabaseDelegate( + new BackupWithClassifiers( db, db.getClassifiers().stream() - .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) + .map(c -> new ClassifierDetails(ClassifierType.NEW, db.getName(), null, new TreeMap<>(c))) .toList() ) ) @@ -720,23 +704,23 @@ protected List getAllDbByFilter(List bac return backupDatabasesToFilter.stream() .map(db -> { - List filteredClassifiers = db.getClassifiers().stream() + List filteredClassifiers = db.getClassifiers().stream() .filter(classifier -> { String namespace = (String) classifier.get(NAMESPACE); String microserviceName = (String) classifier.get(MICROSERVICE_NAME); String type = db.getLogicalBackup().getType(); boolean configurational = db.isConfigurational(); return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) - && filterCriteria.getExclude().stream().filter(this::isFilled).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); + && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) - .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) + .map(c -> new ClassifierDetails(ClassifierType.NEW, db.getName(), null, new TreeMap<>(c))) .toList(); if (filteredClassifiers.isEmpty()) { return null; } - return new BackupDatabaseDelegate(db, filteredClassifiers); + return new BackupWithClassifiers(db, filteredClassifiers); }) .filter(Objects::nonNull) .toList(); @@ -753,7 +737,7 @@ protected Restore initializeFullRestoreStructure( restoreRequest.getFilterCriteria()); // Filter internal database classifiers - List filteredBackupDatabases = getAllDbByFilter( + List filteredBackupDatabases = getAllDbByFilter( backup.getLogicalBackups().stream() .flatMap(logicalBackup -> logicalBackup.getBackupDatabases().stream()) .toList(), @@ -767,16 +751,16 @@ protected Restore initializeFullRestoreStructure( // Mapping classifiers List mappedExternalDbs = executeMappingForExternalDb(filteredExternalDbs, restoreRequest.getMapping()); - List mappedBackupDatabases = + List mappedBackupDatabases = applyMappingToBackupDatabases(filteredBackupDatabases, restoreRequest.getMapping()); checkForCollision(mappedExternalDbs, mappedBackupDatabases); // Enrich classifiers - List enrichedBackupClassifiers = enrichInternalDbClassifiers(mappedBackupDatabases); + List enrichedBackupClassifiers = enrichInternalDbClassifiers(mappedBackupDatabases); List enrichedExternalDbs = enrichExternalDbClassifiers(mappedExternalDbs); // Group BackupDatabases by updated adapter and logicalBackupName - Map> groupedByTypeAndAdapter = + Map> groupedByTypeAndAdapter = groupBackupDatabasesByLogicalBackupNameAndAdapter(enrichedBackupClassifiers); log.info("Initializing restore structure: restoreName={}, backupName={}", @@ -826,41 +810,38 @@ protected Restore initializeFullRestoreStructure( return restore; } - private List applyMappingToBackupDatabases(List backupDatabases, Mapping mapping) { + private List applyMappingToBackupDatabases(List backupDatabases, Mapping mapping) { return backupDatabases.stream() .map(db -> - new BackupDatabaseDelegate(db.backupDatabase(), applyMapping(db.classifiers(), mapping)) + new BackupWithClassifiers(db.backupDatabase(), applyMapping(db.classifiers(), mapping)) ).toList(); } - protected List applyMapping(List classifiers, Mapping mapping) { - List mappedClassifiers = new ArrayList<>(); - - for (Classifier classifier : classifiers) { + protected List applyMapping(List classifiers, Mapping mapping) { + for (ClassifierDetails classifier : classifiers) { SortedMap sourceClassifier = classifier.getClassifierBeforeMapper(); SortedMap mappedClassifier = new TreeMap<>(sourceClassifier); if (mapping != null) { - String targetNamespace = getValue(mapping.getNamespaces(), (String) sourceClassifier.get(NAMESPACE)); + String targetNamespace = applyMapping(mapping.getNamespaces(), (String) sourceClassifier.get(NAMESPACE)); String targetTenant = (String) sourceClassifier.get(TENANT_ID); mappedClassifier.put(NAMESPACE, targetNamespace); if (targetTenant != null) { - targetTenant = getValue(mapping.getTenants(), targetTenant); + targetTenant = applyMapping(mapping.getTenants(), targetTenant); mappedClassifier.put(TENANT_ID, targetTenant); } } classifier.setClassifier(mappedClassifier); - mappedClassifiers.add(classifier); } - return mappedClassifiers; + return classifiers; } - private List enrichInternalDbClassifiers(List backupDatabases) { + private List enrichInternalDbClassifiers(List backupDatabases) { return backupDatabases.stream() .map(db -> { String type = db.backupDatabase().getLogicalBackup().getType(); - List updatedClassifiers = findSimilarDbByClassifier(db.classifiers(), type).stream().toList(); - return new BackupDatabaseDelegate(db.backupDatabase(), updatedClassifiers); + List updatedClassifiers = findSimilarDbByClassifier(db.classifiers(), type).stream().toList(); + return new BackupWithClassifiers(db.backupDatabase(), updatedClassifiers); }).toList(); } @@ -868,7 +849,7 @@ private List enrichExternalDbClassifiers(List enrichedExternalDbs = new ArrayList<>(); for (RestoreExternalDatabase db : externalDatabases) { - List classifiers = findSimilarDbByClassifier(db.getClassifiers(), db.getType()).stream().toList(); + List classifiers = findSimilarDbByClassifier(db.getClassifiers(), db.getType()).stream().toList(); db.setClassifiers(classifiers); enrichedExternalDbs.add(db); } @@ -876,19 +857,19 @@ private List enrichExternalDbClassifiers(List externalDatabases, List backupDatabases) { + private void checkForCollision(List externalDatabases, List backupDatabases) { Set> uniqueClassifiers = new HashSet<>(); - List duplicateClassifiers = new ArrayList<>(); - - for (RestoreExternalDatabase externalDatabase : externalDatabases) { - for (Classifier classifier : externalDatabase.getClassifiers()) - collectDuplicateClassifiers(classifier, uniqueClassifiers, duplicateClassifiers); - } - - for (BackupDatabaseDelegate internalDatabase : backupDatabases) { - for (Classifier classifier : internalDatabase.classifiers()) - collectDuplicateClassifiers(classifier, uniqueClassifiers, duplicateClassifiers); - } + List duplicateClassifiers = new ArrayList<>(); + + Stream.concat( + externalDatabases.stream().flatMap(ext -> ext.getClassifiers().stream()), + backupDatabases.stream().flatMap(db -> db.classifiers().stream()) + ).forEach(classifier -> { + SortedMap c = classifier.getClassifier(); + if (!uniqueClassifiers.add(c)) { + duplicateClassifiers.add(classifier); + } + }); if (!duplicateClassifiers.isEmpty()) { String msg = String.format( @@ -899,27 +880,15 @@ private void checkForCollision(List externalDatabases, } } - private void collectDuplicateClassifiers( - Classifier classifier, - Set> uniqueClassifiers, - List duplicateClassifiers + protected List validateAndFilterExternalDb( + List externalDatabases, + ExternalDatabaseStrategy strategy, + FilterCriteria filterCriteria ) { - SortedMap c = classifier.getClassifier(); - if (!uniqueClassifiers.add(c)) { - duplicateClassifiers.add(classifier); - } - } - - protected List validateAndFilterExternalDb - (List externalDatabases, - ExternalDatabaseStrategy strategy, - FilterCriteria filterCriteria) { if (isEmpty(externalDatabases)) return List.of(); - String externalNames = externalDatabases.stream() - .map(BackupExternalDatabase::getName) - .collect(Collectors.joining(", ")); + String externalNames = extractExternalDbName(externalDatabases, BackupExternalDatabase::getName); return switch (strategy) { case FAIL -> { @@ -939,43 +908,54 @@ private void collectDuplicateClassifiers( yield List.of(); } case INCLUDE -> { - log.info("Including external databases to restore by strategy={}. External db names: [{}]", - ExternalDatabaseStrategy.INCLUDE, externalNames); - if (isFilterEmpty(filterCriteria)) - yield externalDatabases.stream() + List restoreExternalDatabases; + if (isFilterEmpty(filterCriteria)) { + restoreExternalDatabases = externalDatabases.stream() .map(db -> mapper.toRestoreExternalDatabase( db, db.getClassifiers().stream() - .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) + .map(c -> new ClassifierDetails(ClassifierType.NEW, db.getName(), null, new TreeMap<>(c))) .toList() )) .toList(); + } + else { + restoreExternalDatabases = externalDatabases.stream() + .map(db -> { + List filteredClassifiers = db.getClassifiers().stream() + .filter(classifier -> { + String namespace = (String) classifier.get(NAMESPACE); + String microserviceName = (String) classifier.get(MICROSERVICE_NAME); + String type = db.getType(); + return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, false)) + && filterCriteria.getExclude().stream().filter(this::isFilled).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false));//isFilled + }) + .map(c -> new ClassifierDetails(ClassifierType.NEW, db.getName(), null, new TreeMap<>(c))) + .toList(); + + if (filteredClassifiers.isEmpty()) { + return null; + } - yield externalDatabases.stream() - .map(db -> { - List filteredClassifiers = db.getClassifiers().stream() - .filter(classifier -> { - String namespace = (String) classifier.get(NAMESPACE); - String microserviceName = (String) classifier.get(MICROSERVICE_NAME); - String type = db.getType(); - return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, false)) - && filterCriteria.getExclude().stream().filter(this::isFilled).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false)); - }) - .map(c -> new Classifier(ClassifierType.NEW, null, null, new TreeMap<>(c))) - .toList(); - - if (filteredClassifiers.isEmpty()) { - return null; - } - - return mapper.toRestoreExternalDatabase(db, filteredClassifiers); - }) - .filter(Objects::nonNull) - .toList(); + return mapper.toRestoreExternalDatabase(db, filteredClassifiers); + }) + .filter(Objects::nonNull) + .toList(); + } + log.info("Including external databases to restore by strategy={}. External db names: [{}]", + ExternalDatabaseStrategy.INCLUDE, extractExternalDbName(restoreExternalDatabases, RestoreExternalDatabase::getName)); + yield restoreExternalDatabases; } }; } + private String extractExternalDbName(List externalDatabases, Function d) { + return externalDatabases.stream() + .map(d) + .collect(Collectors.joining(", ")); + } + + private List executeMappingForExternalDb( List externalDatabases, Mapping mapping @@ -983,7 +963,7 @@ private List executeMappingForExternalDb( List updatedExternals = new ArrayList<>(); for (RestoreExternalDatabase externalDatabase : externalDatabases) { - List mappedClassifiers = applyMapping(externalDatabase.getClassifiers(), mapping); + List mappedClassifiers = applyMapping(externalDatabase.getClassifiers(), mapping); externalDatabase.setClassifiers(mappedClassifiers); updatedExternals.add(externalDatabase); } @@ -991,12 +971,12 @@ private List executeMappingForExternalDb( } private List createRestoreDatabases( - List backupDatabases + List backupDatabases ) { return backupDatabases.stream() .map(delegatedBackupDatabase -> { BackupDatabase backupDatabase = delegatedBackupDatabase.backupDatabase(); - List classifiers = delegatedBackupDatabase.classifiers(); + List classifiers = delegatedBackupDatabase.classifiers(); String namespace = (String) classifiers.getFirst().getClassifier().get(NAMESPACE); String bgVersion = null; if (backupDatabase.isConfigurational()) { @@ -1006,7 +986,7 @@ private List createRestoreDatabases( } List users = backupDatabase.getUsers().stream() - .map(u -> new RestoreDatabase.User(u.getName(), u.getRole())) + .map(u -> new RestoreDatabase.User(null, u.getRole())) .toList(); RestoreDatabase restoreDatabase = new RestoreDatabase(); @@ -1023,15 +1003,15 @@ private List createRestoreDatabases( .toList(); } - private String getValue(Map map, String oldValue) { + private String applyMapping(Map map, String oldValue) { if (map == null || map.isEmpty()) { return oldValue; } return map.getOrDefault(oldValue, oldValue); } - private Map> groupBackupDatabasesByLogicalBackupNameAndAdapter( - List backupDatabases + private Map> groupBackupDatabasesByLogicalBackupNameAndAdapter( + List backupDatabases ) { return backupDatabases.stream() .map(this::mapToAdapterBackupKeyEntry) @@ -1041,38 +1021,23 @@ private Map> groupBackupDatabases )); } - private Map.Entry mapToAdapterBackupKeyEntry( - BackupDatabaseDelegate backupDatabaseDelegate + private Map.Entry mapToAdapterBackupKeyEntry( + BackupWithClassifiers backupDatabaseDelegate ) { - List classifiers = backupDatabaseDelegate.classifiers(); + List classifiers = backupDatabaseDelegate.classifiers(); String type = backupDatabaseDelegate.backupDatabase().getLogicalBackup().getType(); String logicalBackupName = backupDatabaseDelegate.backupDatabase().getLogicalBackup().getLogicalBackupName(); - Optional adapterIdFromClassifier = classifiers.stream() - .filter(c -> ClassifierType.REPLACED == c.getType() || ClassifierType.TRANSIENT_REPLACED == c.getType()) - .map(Classifier::getClassifier) + SortedMap firstNewClassifier = classifiers.stream() + .map(ClassifierDetails::getClassifier) .filter(Objects::nonNull) - .map(c -> databaseRegistryDbaasRepository.getDatabaseByClassifierAndType(c, type)) - .filter(Optional::isPresent) - .map(opt -> opt.get().getDatabase().getAdapterId()) - .findFirst(); - - String adapterId; - if (adapterIdFromClassifier.isPresent()) { - adapterId = adapterIdFromClassifier.get(); - } else { - SortedMap firstNewClassifier = classifiers.stream() - .filter(c -> ClassifierType.NEW == c.getType()) - .map(Classifier::getClassifier) - .filter(Objects::nonNull) - .findFirst() - .orElseGet(() -> classifiers.getFirst().getClassifier()); - String targetNamespace = (String) firstNewClassifier.get(NAMESPACE); - String microserviceName = (String) firstNewClassifier.get(MICROSERVICE_NAME); - PhysicalDatabase physicalDatabase = balancingRulesService - .applyBalancingRules(type, targetNamespace, microserviceName); - adapterId = physicalDatabase.getAdapter().getAdapterId(); - } + .findFirst() + .orElseGet(() -> classifiers.getFirst().getClassifier()); + String targetNamespace = (String) firstNewClassifier.get(NAMESPACE); + String microserviceName = (String) firstNewClassifier.get(MICROSERVICE_NAME); + PhysicalDatabase physicalDatabase = balancingRulesService + .applyBalancingRules(type, targetNamespace, microserviceName); + String adapterId = physicalDatabase.getAdapter().getAdapterId(); // Checking adapter support backup restore DbaasAdapter adapter = physicalDatabasesService.getAdapterById(adapterId); @@ -1083,7 +1048,7 @@ private Map.Entry mapToAdapterBackupKe } return Map.entry(new AdapterBackupKey(adapterId, logicalBackupName), - new BackupDatabaseDelegate(backupDatabaseDelegate.backupDatabase(), classifiers)); + new BackupWithClassifiers(backupDatabaseDelegate.backupDatabase(), classifiers)); } protected void startRestore(Restore restore, boolean dryRun) { @@ -1121,7 +1086,7 @@ private CompletableFuture runLogicalRestoreAsync(LogicalRestore logicalRes .thenAccept(response -> refreshLogicalRestoreState(logicalRestore, response)) .exceptionally(throwable -> { - logicalRestore.setStatus(RestoreTaskStatus.FAILED_WITH_RETRY); + logicalRestore.setStatus(RestoreTaskStatus.RETRYABLE_FAIL); logicalRestore.setErrorMessage(extractErrorMessage(throwable)); log.error("Logical restore failed: adapterId={}, error={}", logicalRestore.getAdapterId(), logicalRestore.getErrorMessage()); @@ -1247,7 +1212,7 @@ private LogicalRestoreAdapterResponse executeRestore( protected void checkRestoresAsync() { log.info("Starting restore scheduler"); LockAssert.assertLocked(); - List restoresToAggregate = restoreRepository.findRestoresToAggregate(); + List restoresToAggregate = restoreRepository.findRestoresToTrack(); log.info("Found restores to aggregate {}", restoresToAggregate.stream().map(Restore::getName).toList()); @@ -1297,15 +1262,16 @@ protected List ensureUsers(String adapterId, try { return Failsafe.with(retryPolicy) .get(() -> { - EnsuredUser ensuredUser = adapter.ensureUser(user.getName(), null, dbName, user.getRole()); + EnsuredUser ensuredUser = adapter.ensureUser(null, null, dbName, user.getRole()); + user.setName(ensuredUser.getName()); log.info("User ensured for database=[{}], user=[name:{}, connectionProperties:{}]", dbId, ensuredUser.getName(), ensuredUser.getConnectionProperties()); return ensuredUser; }); } catch (Exception e) { - log.error("Failed to ensure user [{}] in database [{}]", user.getName(), dbId); + log.error("Failed to ensure user in database [{}]", dbId); throw new BackupExecutionException( - String.format("Failed to ensure user [%s], in database [%s]", user.getName(), dbId), e); + String.format("Failed to ensure user in database [%s]", dbId), e); } }) .toList(); @@ -1329,8 +1295,8 @@ private void fetchStatuses(Restore restore) { List notFinishedLogicalRestores = restore.getLogicalRestores().stream() .filter(db -> db.getStatus() == RestoreTaskStatus.IN_PROGRESS - || db.getStatus() == RestoreTaskStatus.NOT_STARTED - || db.getStatus() == RestoreTaskStatus.FAILED_WITH_RETRY + || db.getStatus() == NOT_STARTED + || db.getStatus() == RestoreTaskStatus.RETRYABLE_FAIL ).toList(); log.debug("Starting checking status for logical restores: restore={}, logicalRestores={}", restore.getName(), @@ -1345,7 +1311,7 @@ private void fetchStatuses(Restore restore) { asyncOperations.wrapWithContext( () -> Failsafe.with(retryPolicy).get(() -> { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalRestore.getAdapterId()); - if (logicalRestore.getLogicalRestoreName() == null || logicalRestore.getLogicalRestoreName().isBlank()) { + if (isNameEmpty(logicalRestore.getLogicalRestoreName())) { LogicalRestoreAdapterResponse response = logicalRestore(logicalRestore, restore.getStorageName(), restore.getBlobPath(), false); refreshLogicalRestoreState(logicalRestore, response); } @@ -1356,7 +1322,7 @@ private void fetchStatuses(Restore restore) { .thenAccept(response -> refreshLogicalRestoreState(logicalRestore, response)) .exceptionally(throwable -> { - logicalRestore.setStatus(RestoreTaskStatus.FAILED_WITH_RETRY); + logicalRestore.setStatus(RestoreTaskStatus.RETRYABLE_FAIL); logicalRestore.setErrorMessage(throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage()); return null; @@ -1374,7 +1340,6 @@ private void aggregateRestoreStatus(Restore restore) { int totalDbCount = 0; int countCompletedDb = 0; - long totalDuration = 0; List errorMessages = new ArrayList<>(); for (LogicalRestore lr : logicalRestoreList) { @@ -1393,14 +1358,12 @@ private void aggregateRestoreStatus(Restore restore) { for (RestoreDatabase restoreDatabase : dbs) { totalDbCount += 1; countCompletedDb += RestoreTaskStatus.COMPLETED == restoreDatabase.getStatus() ? 1 : 0; - totalDuration += restoreDatabase.getDuration(); } } - restore.setStatus(aggregateRestoreStatus(statusSet)); + restore.setStatus(aggregateRestoreTaskStatus(statusSet)); restore.setTotal(totalDbCount); restore.setCompleted(countCompletedDb); - restore.setDuration(totalDuration); restore.setErrorMessage(String.join("; ", errorMessages)); log.info("Aggregated restore status: restoreName={}, status={}, totalDb={}, completed={}, errorMessage={}", restore.getName(), restore.getStatus(), restore.getTotal(), restore.getCompleted(), restore.getErrorMessage()); @@ -1424,7 +1387,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, restoreDatabase.getName(), restoreDatabase.getSettings(), restoreDatabase.getClassifiers().stream() - .map(Classifier::getClassifier).collect(Collectors.toSet()), + .map(ClassifierDetails::getClassifier).collect(Collectors.toSet()), type, false, false, @@ -1449,7 +1412,7 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, externalDatabase.getName(), null, externalDatabase.getClassifiers().stream() - .map(Classifier::getClassifier).collect(Collectors.toSet()), + .map(ClassifierDetails::getClassifier).collect(Collectors.toSet()), type, true, true, @@ -1463,8 +1426,8 @@ protected void initializeLogicalDatabasesFromRestore(Restore restore, log.info("Finished initializing logical databases from restore {}", restore.getName()); } - private Set findSimilarDbByClassifier(List classifiers, String type) { - Set uniqueClassifiers = new HashSet<>(); + private Set findSimilarDbByClassifier(List classifiers, String type) { + Set uniqueClassifiers = new HashSet<>(); classifiers.forEach(classifier -> { SortedMap currClassifier = classifier.getClassifier(); log.debug("Classifier candidate: {}", currClassifier); @@ -1478,13 +1441,13 @@ private Set findSimilarDbByClassifier(List classifiers, classifier.setType(ClassifierType.REPLACED); classifier.setPreviousDatabase(db.getName()); log.info("Found existing database {} for classifier {}", db.getId(), currClassifier); - Set existClassifiers = db.getDatabaseRegistry().stream() + Set existClassifiers = db.getDatabaseRegistry().stream() .map(AbstractDatabaseRegistry::getClassifier) - .filter(c -> !c.containsKey(MARKED_FOR_DROP)) // TODO + .filter(c -> !c.containsKey(MARKED_FOR_DROP)) .map(TreeMap::new) .map(c -> currClassifier.equals(c) ? classifier - : new Classifier(ClassifierType.TRANSIENT_REPLACED, db.getName(), c, null) + : new ClassifierDetails(ClassifierType.TRANSIENT_REPLACED, db.getName(), new TreeMap<>(c), null) ) .collect(Collectors.toSet()); @@ -1495,7 +1458,7 @@ private Set findSimilarDbByClassifier(List classifiers, return uniqueClassifiers; } - private void findAndMarkDatabaseAsOrphan(List classifiers, String type) { + private void findAndMarkDatabaseAsOrphan(List classifiers, String type) { classifiers.stream() .filter(classifier -> ClassifierType.REPLACED == classifier.getType() || ClassifierType.TRANSIENT_REPLACED == classifier.getType() @@ -1628,7 +1591,7 @@ private void retryRestore(Restore restore) { || logicalRestore.getLogicalRestoreName().isEmpty() ) .forEach(logicalRestore -> { - logicalRestore.setStatus(RestoreTaskStatus.FAILED_WITH_RETRY); + logicalRestore.setStatus(RestoreTaskStatus.RETRYABLE_FAIL); logicalRestore.setLogicalRestoreName(null); }); } @@ -1708,6 +1671,14 @@ protected Map> validateAndFilterDatabasesForBac return filteredDatabases; } + @Transactional + public void deleteBackup(String backupName) { + if (dbaaSHelper.isProductionMode()) { + throw new ForbiddenDeleteOperationException(); + } + backupRepository.deleteById(backupName); + } + private boolean isBackupRestoreUnsupported(DbaasAdapter adapter) { return !adapter.isBackupRestoreSupported(); } @@ -1740,12 +1711,12 @@ private T restoreLockWrapper(Supplier action) { Optional optLock = lockProvider.lock(config); if (optLock.isEmpty()) - throw new OperationAlreadyRunningException("restore", Source.builder().build()); + throw new OperationAlreadyRunningException("restore"); // Start locking action SimpleLock lock = optLock.get(); try { if (restoreRepository.countNotCompletedRestores() > 0) - throw new OperationAlreadyRunningException("restore", Source.builder().build()); + throw new OperationAlreadyRunningException("restore"); return action.get(); } finally { try { @@ -1756,8 +1727,8 @@ private T restoreLockWrapper(Supplier action) { } } - private RetryPolicy buildRetryPolicy(String operation, String idType, String id, String adapterId) { - String context = String.format("%s operation [%s=%s, adapter=%s]", operation, idType, id, adapterId); + private RetryPolicy buildRetryPolicy(String operation, String entityType, String entityId, String adapterId) { + String context = String.format("%s operation [%s=%s, adapter=%s]", operation, entityType, entityId, adapterId); return new RetryPolicy<>() .handle(WebApplicationException.class) .withMaxRetries(retryAttempts) @@ -1793,25 +1764,36 @@ private boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } - private static , R extends Enum> R aggregateStatus( - Set statusSet, - Function taskStatusGetter, - Function resultStatusGetter) { + private boolean isNameEmpty(String name) { + return name == null || name.isBlank(); + } - if (statusSet.contains(taskStatusGetter.apply("NOT_STARTED")) && statusSet.size() > 1) - return resultStatusGetter.apply("IN_PROGRESS"); + protected BackupStatus aggregateBackupTaskStatus(Set backupTaskStatuses) { + if (backupTaskStatuses.contains(BackupTaskStatus.NOT_STARTED) || + backupTaskStatuses.contains(BackupTaskStatus.RETRYABLE_FAIL) || + backupTaskStatuses.contains(BackupTaskStatus.IN_PROGRESS)) + return BackupStatus.IN_PROGRESS; - if (statusSet.contains(taskStatusGetter.apply("FAILED_WITH_RETRY"))) - return resultStatusGetter.apply("IN_PROGRESS"); + if (backupTaskStatuses.contains(BackupTaskStatus.FAILED)) + return BackupStatus.FAILED; - if (statusSet.contains(taskStatusGetter.apply("IN_PROGRESS"))) - return resultStatusGetter.apply("IN_PROGRESS"); + if (backupTaskStatuses.contains(BackupTaskStatus.COMPLETED)) + return BackupStatus.COMPLETED; + return null; + } - if (statusSet.contains(taskStatusGetter.apply("FAILED"))) - return resultStatusGetter.apply("FAILED"); + protected RestoreStatus aggregateRestoreTaskStatus(Set backupTaskStatuses) { + if (backupTaskStatuses.contains(RestoreTaskStatus.NOT_STARTED) || + backupTaskStatuses.contains(RestoreTaskStatus.RETRYABLE_FAIL) || + backupTaskStatuses.contains(RestoreTaskStatus.IN_PROGRESS)) + return RestoreStatus.IN_PROGRESS; - if (statusSet.contains(taskStatusGetter.apply("COMPLETED"))) - return resultStatusGetter.apply("COMPLETED"); + if (backupTaskStatuses.contains(RestoreTaskStatus.FAILED)) + return RestoreStatus.FAILED; + + if (backupTaskStatuses.contains(RestoreTaskStatus.COMPLETED)) + return RestoreStatus.COMPLETED; return null; } + } diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java index 6fd22a4a..343d8fa6 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java @@ -83,7 +83,7 @@ void initiateBackup_invalidDto() { .statusCode(BAD_REQUEST.getStatusCode()) .body("message", allOf( containsString("backupName: must not be blank"), - containsString("filter: size must be between 1 and 2147483647") + containsString("filter: there should be at least one filter specified") )); verify(dbBackupV2Service, times(0)).backup(any(), anyBoolean()); @@ -108,7 +108,7 @@ void initiateBackup_databasesCannotBackup_ignoreNotBackupableDatabaseFalse() { .when().post("/backup") .then() .statusCode(422) - .body("reason", equalTo("Backup not allowed")) + .body("reason", equalTo("Operation not allowed")) .body("message", equalTo("The backup/restore request can't be processed. Backup operation unsupported for databases: " + dbNames)); verify(dbBackupV2Service, times(1)).backup(backupRequest, false); } @@ -137,7 +137,7 @@ void initiateBackup_backupAlreadyExists() { } @Test - void initiateBackup_emptyFilterCase(){ + void initiateBackup_emptyFilterCase() { String namespace = "namespace"; String backupName = "backupName"; @@ -153,12 +153,16 @@ void initiateBackup_emptyFilterCase(){ .then() .statusCode(BAD_REQUEST.getStatusCode()) .body("reason", equalTo("Request does not contain required fields")) - .body("message", equalTo("filter[0]: Filter must have at least one non-null field")); + .body("message", allOf( + containsString("exclude[0]: Filter must have at least one non-null field"), + containsString("filter[0]: Filter must have at least one non-null field") + ) + ); verify(dbBackupV2Service, times(0)).backup(backupRequest, false); } @Test - void restoreBackup_emptyFilterCase(){ + void restoreBackup_emptyFilterCase() { String namespace = "namespace"; String restoreName = "restoreName"; String backupName = "backupName"; @@ -176,9 +180,14 @@ void restoreBackup_emptyFilterCase(){ .then() .statusCode(BAD_REQUEST.getStatusCode()) .body("reason", equalTo("Request does not contain required fields")) - .body("message", equalTo("filter[0]: Filter must have at least one non-null field")); + .body("message", allOf( + containsString("exclude[0]: Filter must have at least one non-null field"), + containsString("filter[0]: Filter must have at least one non-null field") + ) + ); verify(dbBackupV2Service, times(0)).restore(backupName, restoreRequest, false); } + @Test void getBackupStatus_validBackupNameCase() { String backupName = "test-backup-name"; diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index f7bcf980..dda4babc 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -2,7 +2,7 @@ import com.netcracker.cloud.dbaas.dto.EnsuredUser; import com.netcracker.cloud.dbaas.dto.backupV2.*; -import com.netcracker.cloud.dbaas.entity.dto.backupV2.BackupDatabaseDelegate; +import com.netcracker.cloud.dbaas.entity.dto.backupV2.BackupWithClassifiers; import com.netcracker.cloud.dbaas.entity.dto.backupV2.LogicalBackupAdapterResponse; import com.netcracker.cloud.dbaas.entity.dto.backupV2.LogicalRestoreAdapterResponse; import com.netcracker.cloud.dbaas.entity.pg.*; @@ -95,6 +95,8 @@ class DbBackupV2ServiceTest { @Inject @Named("process-orchestrator") private DataSource dataSource; + @InjectMock + private DbaaSHelper dbaaSHelper; @BeforeEach void setUp() { @@ -736,7 +738,7 @@ void backup_dryRunTrue() { "tenant".equals(classifier.get(SCOPE)) && tenantId.equals(classifier.get(TENANT_ID))); BackupDatabaseResponse.User user = backupDatabase.getUsers().getFirst(); - assertEquals("username", user.getName()); + assertEquals("oldUsername", user.getName()); assertEquals("admin", user.getRole()); Backup backup = backupRepository.findById(backupName); @@ -882,8 +884,8 @@ void restore_withoutMapping_finishedWithStatusCompleted() { user2.setConnectionProperties(Map.of()); user2.setResources(List.of(resource2)); - when(dbaasAdapter1.ensureUser("username", null, newName1, "admin")).thenReturn(user1); - when(dbaasAdapter2.ensureUser("username", null, newName2, "admin")).thenReturn(user2); + when(dbaasAdapter1.ensureUser(null, null, newName1, "admin")).thenReturn(user1); + when(dbaasAdapter2.ensureUser(null, null, newName2, "admin")).thenReturn(user2); RestoreResponse restoreResponse = dbBackupV2Service.restore( backupName, @@ -906,7 +908,6 @@ void restore_withoutMapping_finishedWithStatusCompleted() { assertEquals(2, restore.getTotal()); assertEquals(2, restore.getCompleted()); assertTrue(restore.getErrorMessage().isBlank()); - assertEquals(2, restore.getDuration()); assertEquals(1, restore.getAttemptCount()); assertEquals(2, restore.getLogicalRestores().size()); assertEquals(0, restore.getExternalDatabases().size()); @@ -1158,8 +1159,8 @@ void restore_withMapping_finishedWithStatusCompleted() { user2.setConnectionProperties(Map.of()); user2.setResources(List.of(resource2)); - when(dbaasAdapter1.ensureUser("username", null, newName1, "admin")).thenReturn(user1); - when(dbaasAdapter2.ensureUser("username", null, newName2, "admin")).thenReturn(user2); + when(dbaasAdapter1.ensureUser(null, null, newName1, "admin")).thenReturn(user1); + when(dbaasAdapter2.ensureUser(null, null, newName2, "admin")).thenReturn(user2); Map namespaceMap = Map.of(namespace, mappedNamespace); @@ -1186,7 +1187,6 @@ void restore_withMapping_finishedWithStatusCompleted() { assertEquals(2, restore.getTotal()); assertEquals(2, restore.getCompleted()); assertTrue(restore.getErrorMessage().isBlank()); - assertEquals(2, restore.getDuration()); assertEquals(1, restore.getAttemptCount()); assertEquals(2, restore.getLogicalRestores().size()); assertEquals(1, restore.getExternalDatabases().size()); @@ -1432,7 +1432,7 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { physicalDatabase1.setType(postgresType); physicalDatabase1.setPhysicalDatabaseIdentifier("postgres-dev"); - when(balancingRulesService.applyBalancingRules(postgresType, mappedNamespace, microserviceName1)) + when(balancingRulesService.applyBalancingRules(eq(postgresType), anyString(), anyString())) .thenReturn(physicalDatabase1); when(physicalDatabasesService.getByAdapterId(adapterId)).thenReturn(physicalDatabase1); @@ -1472,14 +1472,15 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { DbResource resource1 = new DbResource(); resource1.setId(UUID.randomUUID()); resource1.setKind("kind"); - resource1.setName("name"); + resource1.setName("newName"); EnsuredUser user1 = new EnsuredUser(); user1.setConnectionProperties(Map.of( - "key", "value" + "username", "newName" )); user1.setResources(List.of(resource1)); + user1.setName("newName"); - when(dbaasAdapter.ensureUser("username", null, newName, "admin")).thenReturn(user1); + when(dbaasAdapter.ensureUser(null, null, newName, "admin")).thenReturn(user1); Map namespaceMap = Map.of(namespace, mappedNamespace); Map tenantMap = Map.of(tenantId, mappedTenantId); @@ -1490,6 +1491,8 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { ); dbBackupV2Service.checkRestoresAsync(); + Restore restore = restoreRepository.findById(restoreName); + assertEquals("newName", restore.getLogicalRestores().getFirst().getRestoreDatabases().getFirst().getUsers().getFirst().getName()); List databaseRegistries = databaseRegistryDbaasRepository.findAnyLogDbRegistryTypeByNamespace(mappedNamespace); assertEquals(4, databaseRegistries.size()); @@ -1513,6 +1516,7 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { assertFalse(database1.isExternallyManageable()); assertFalse(database1.isMarkedForDrop()); assertEquals(newName, database1.getName()); + assertEquals("newName", database1.getConnectionProperties().getFirst().get("username")); // Registry that was copied from DB that was MARKED_FOR_DROP DatabaseRegistry databaseRegistry2 = database1.getDatabaseRegistry().stream() @@ -1562,6 +1566,7 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { assertFalse(database3.isExternallyManageable()); assertTrue(database3.isMarkedForDrop()); assertEquals(newName, database3.getName()); + assertEquals("oldUsername", database3.getConnectionProperties().getFirst().get("username")); DatabaseRegistry databaseRegistry5 = database3.getDatabaseRegistry().stream() .filter(db -> anotherNamespace.equals(db.getNamespace())) @@ -1577,196 +1582,6 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { ); } - @Test - void restore_testChoosingAdapterBasedClassifierType() { - String restoreName = "restoreName"; - String backupName = "backupName"; - String logicalBackupName1 = "logicalBackupName"; - String logicalBackupName2 = "logicalBackupName2"; - String logicalRestoreName1 = "logicalRestoreName"; - String logicalRestoreName2 = "logicalRestoreName2"; - - String dbName1 = "dbName"; - String dbName2 = "dbName2"; - String newName1 = "newName"; - String newName2 = "newName2"; - String adapterId1 = "adapterId1"; - String adapterId2 = "adapterId2"; - - String namespace1 = "namespace1"; - String namespace2 = "namespace2"; - String mappedNamespace = "mappedNamespace"; - String anotherNamespace = "anotherNamespace"; - - String microserviceName1 = "microserviceName1"; - String microserviceName2 = "microserviceName2"; - - String tenantId = "tenantId"; - String anotherTenantId = "tenantId"; - String mappedTenantId = "mappedTenantId"; - String postgresType = "postgresql"; - - // Database with registries that exists in another namespace - Database database = getDatabase(adapterId1, newName1, false, false, null); - DatabaseRegistry registry1 = getDatabaseRegistry(database, mappedNamespace, microserviceName1, mappedTenantId, postgresType); - DatabaseRegistry registry2 = getDatabaseRegistry(database, anotherNamespace, microserviceName1, anotherTenantId, postgresType); - - databaseRegistryDbaasRepository.saveAnyTypeLogDb(registry1); - - BackupDatabase backupDatabase = getBackupDatabase(dbName1, List.of(getClassifier(namespace1, microserviceName1, tenantId)), false, BackupTaskStatus.COMPLETED, null); - BackupDatabase backupDatabase2 = getBackupDatabase(dbName2, List.of(getClassifier(namespace2, microserviceName2, null)), false, BackupTaskStatus.COMPLETED, null); - LogicalBackup logicalBackup = getLogicalBackup(logicalBackupName1, adapterId1, postgresType, List.of(backupDatabase), BackupTaskStatus.COMPLETED, null); - LogicalBackup logicalBackup2 = getLogicalBackup(logicalBackupName2, adapterId2, postgresType, List.of(backupDatabase2), BackupTaskStatus.COMPLETED, null); - Backup backup = getBackup(backupName, ExternalDatabaseStrategy.INCLUDE, getFilterCriteriaEntity(List.of(namespace1, namespace2)), List.of(logicalBackup, logicalBackup2), List.of(), BackupStatus.COMPLETED, null); - backupRepository.save(backup); - - DbaasAdapter dbaasAdapter1 = Mockito.mock(DbaasAdapter.class); - DbaasAdapter dbaasAdapter2 = Mockito.mock(DbaasAdapter.class); - - when(physicalDatabasesService.getAdapterById(adapterId1)).thenReturn(dbaasAdapter1); - when(dbaasAdapter1.isBackupRestoreSupported()).thenReturn(true); - - when(physicalDatabasesService.getAdapterById(adapterId2)).thenReturn(dbaasAdapter2); - when(dbaasAdapter2.isBackupRestoreSupported()).thenReturn(true); - - // Mock logic of choosing adapter in new/current env - ExternalAdapterRegistrationEntry adapter1 = new ExternalAdapterRegistrationEntry(); - PhysicalDatabase physicalDatabase1 = new PhysicalDatabase(); - physicalDatabase1.setAdapter(adapter1); - physicalDatabase1.setType(postgresType); - physicalDatabase1.setPhysicalDatabaseIdentifier("postgres-dev"); - - when(physicalDatabasesService.getByAdapterId(adapterId1)).thenReturn(physicalDatabase1); - - ExternalAdapterRegistrationEntry adapter2 = new ExternalAdapterRegistrationEntry(); - adapter2.setAdapterId(adapterId2); - PhysicalDatabase physicalDatabase2 = new PhysicalDatabase(); - physicalDatabase2.setAdapter(adapter2); - physicalDatabase2.setType(postgresType); - physicalDatabase2.setPhysicalDatabaseIdentifier("postgres-dev"); - - when(balancingRulesService.applyBalancingRules(postgresType, namespace2, microserviceName2)) - .thenReturn(physicalDatabase2); - when(physicalDatabasesService.getByAdapterId(adapterId2)).thenReturn(physicalDatabase2); - - - // Response during the sync restore process - LogicalRestoreAdapterResponse response = LogicalRestoreAdapterResponse.builder() - .status(IN_PROGRESS_STATUS) - .restoreId(logicalRestoreName1) - .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() - .status(IN_PROGRESS_STATUS) - .previousDatabaseName(dbName1) - .databaseName(newName1) - .duration(1) - .build())) - .build(); - - LogicalRestoreAdapterResponse response2 = LogicalRestoreAdapterResponse.builder() - .status(IN_PROGRESS_STATUS) - .restoreId(logicalRestoreName2) - .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() - .status(IN_PROGRESS_STATUS) - .previousDatabaseName(dbName2) - .databaseName(newName2) - .duration(1) - .build())) - .build(); - - // Response to DryRun, RealRun - when(dbaasAdapter1.restoreV2(eq(logicalBackupName1), anyBoolean(), any())) - .thenReturn(response) - .thenReturn(response); - when(dbaasAdapter2.restoreV2(eq(logicalBackupName2), anyBoolean(), any())) - .thenReturn(response2) - .thenReturn(response2); - - // Response during the async restore process - LogicalRestoreAdapterResponse response3 = LogicalRestoreAdapterResponse.builder() - .status(COMPLETED_STATUS) - .restoreId(logicalRestoreName1) - .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() - .status(COMPLETED_STATUS) - .previousDatabaseName(dbName1) - .databaseName(newName1) - .duration(1) - .build())) - .build(); - - LogicalRestoreAdapterResponse response4 = LogicalRestoreAdapterResponse.builder() - .status(COMPLETED_STATUS) - .restoreId(logicalRestoreName2) - .databases(List.of(LogicalRestoreAdapterResponse.RestoreDatabaseResponse.builder() - .status(COMPLETED_STATUS) - .previousDatabaseName(dbName2) - .databaseName(newName2) - .duration(1) - .build())) - .build(); - - when(dbaasAdapter1.trackRestoreV2(eq(logicalRestoreName1), any(), any())) - .thenReturn(response3); - when(dbaasAdapter2.trackRestoreV2(eq(logicalRestoreName2), any(), any())) - .thenReturn(response4); - - // Mocks to ensure user process - DbResource resource1 = new DbResource(); - resource1.setId(UUID.randomUUID()); - resource1.setKind("kind"); - resource1.setName("name"); - EnsuredUser user1 = new EnsuredUser(); - user1.setConnectionProperties(Map.of( - "key", "value" - )); - user1.setResources(List.of(resource1)); - - when(dbaasAdapter1.ensureUser("username", null, newName1, "admin")).thenReturn(user1); - - DbResource resource2 = new DbResource(); - resource2.setId(UUID.randomUUID()); - resource2.setKind("kind"); - resource2.setName("name"); - EnsuredUser user2 = new EnsuredUser(); - user2.setConnectionProperties(Map.of( - "key", "value" - )); - user2.setResources(List.of(resource2)); - - when(dbaasAdapter2.ensureUser("username", null, newName2, "admin")).thenReturn(user2); - - Map namespaceMap = Map.of(namespace1, mappedNamespace); - Map tenantMap = Map.of(tenantId, mappedTenantId); - - RestoreResponse restoreResponse = dbBackupV2Service.restore(backupName, - getRestoreRequest(restoreName, List.of(namespace1, namespace2), ExternalDatabaseStrategy.INCLUDE, namespaceMap, tenantMap), - false - ); - dbBackupV2Service.checkRestoresAsync(); - - List databaseRegistries = databaseRegistryDbaasRepository.findAllDatabaseRegistersAnyLogType(); - assertEquals(5, databaseRegistries.size()); - - DatabaseRegistry replacedRegistry = databaseRegistries.stream() - .filter(r -> !r.getClassifier().containsKey(MARKED_FOR_DROP)) - .filter(r -> mappedNamespace.equals(r.getClassifier().get(NAMESPACE))) - .findAny().orElse(null); - assertNotNull(replacedRegistry); - assertEquals(adapterId1, replacedRegistry.getAdapterId()); - - DatabaseRegistry transientRegistry = databaseRegistries.stream() - .filter(r -> !r.getClassifier().containsKey(MARKED_FOR_DROP)) - .filter(r -> anotherNamespace.equals(r.getClassifier().get(NAMESPACE))) - .findAny().orElse(null); - assertNotNull(transientRegistry); - assertEquals(adapterId1, transientRegistry.getAdapterId()); - - DatabaseRegistry newRegistry = databaseRegistries.stream() - .filter(r -> namespace2.equals(r.getClassifier().get(NAMESPACE))) - .findAny().orElse(null); - assertNotNull(newRegistry); - assertEquals(adapterId2, newRegistry.getAdapterId()); - } - @Test void restore_dryRun() { String restoreName = "restoreName"; @@ -1821,7 +1636,7 @@ void restore_dryRun() { physicalDatabase1.setType(postgresType); physicalDatabase1.setPhysicalDatabaseIdentifier("postgres-dev"); - when(balancingRulesService.applyBalancingRules(postgresType, mappedNamespace, microserviceName1)) + when(balancingRulesService.applyBalancingRules(eq(postgresType), anyString(), anyString())) .thenReturn(physicalDatabase1); when(physicalDatabasesService.getByAdapterId(adapterId)).thenReturn(physicalDatabase1); @@ -1866,7 +1681,7 @@ void restore_dryRun() { assertEquals(1, restoreDatabase.getSettings().size()); assertEquals(2, restoreDatabase.getClassifiers().size()); - ClassifierResponse classifier1 = restoreDatabase.getClassifiers().stream() + ClassifierDetailsResponse classifier1 = restoreDatabase.getClassifiers().stream() .filter(classifier -> ClassifierType.REPLACED == classifier.getType()) .findAny().orElse(null); assertNotNull(classifier1); @@ -1886,7 +1701,7 @@ void restore_dryRun() { tenantId.equals(classifier1.getClassifierBeforeMapper().get(TENANT_ID)) ); - ClassifierResponse classifier2 = restoreDatabase.getClassifiers().stream() + ClassifierDetailsResponse classifier2 = restoreDatabase.getClassifiers().stream() .filter(classifier -> ClassifierType.TRANSIENT_REPLACED == classifier.getType()) .findAny().orElse(null); assertNotNull(classifier2); @@ -1906,7 +1721,7 @@ void restore_dryRun() { assertEquals(postgresType, externalDatabaseResponse.getType()); assertEquals(1, externalDatabaseResponse.getClassifiers().size()); - ClassifierResponse classifier3 = externalDatabaseResponse.getClassifiers().getFirst(); + ClassifierDetailsResponse classifier3 = externalDatabaseResponse.getClassifiers().getFirst(); assertNotNull(classifier3); assertEquals(ClassifierType.REPLACED, classifier3.getType()); assertEquals(externalDb.getName(), classifier3.getPreviousDatabase()); @@ -1963,9 +1778,9 @@ void retryRestore() { Backup backup = getBackup(backupName, ExternalDatabaseStrategy.INCLUDE, getFilterCriteriaEntity(List.of(namespace)), List.of(logicalBackup), List.of(externalDatabase), BackupStatus.COMPLETED, "Internal Server Error"); backupRepository.save(backup); - Classifier classifierWrapper1 = getClassifier(ClassifierType.NEW, mappedNamespace, microserviceName1, null, namespace, null); - Classifier classifierWrapper2 = getClassifier(ClassifierType.NEW, namespace, microserviceName2, null, namespace, null); - Classifier classifierWrapper3 = getClassifier(ClassifierType.NEW, namespace, microserviceName3, null, namespace, null); + ClassifierDetails classifierWrapper1 = getClassifier(ClassifierType.NEW, mappedNamespace, microserviceName1, null, namespace, null); + ClassifierDetails classifierWrapper2 = getClassifier(ClassifierType.NEW, namespace, microserviceName2, null, namespace, null); + ClassifierDetails classifierWrapper3 = getClassifier(ClassifierType.NEW, namespace, microserviceName3, null, namespace, null); RestoreExternalDatabase restoreExternalDatabase = getRestoreExternalDb(externalName, postgresType, List.of(classifierWrapper3)); RestoreDatabase restoreDatabase1 = getRestoreDatabase(backupDatabase1, newName, List.of(classifierWrapper1), Map.of(), null, RestoreTaskStatus.FAILED, 1, "Internal Server Error"); RestoreDatabase restoreDatabase2 = getRestoreDatabase(backupDatabase2, newName2, List.of(classifierWrapper2), Map.of(), null, RestoreTaskStatus.COMPLETED, 1, null); @@ -2013,26 +1828,26 @@ void retryRestore() { DbResource resource1 = new DbResource(); resource1.setId(UUID.randomUUID()); resource1.setKind("kind"); - resource1.setName("name"); + resource1.setName("newName"); EnsuredUser user1 = new EnsuredUser(); user1.setConnectionProperties(Map.of( "key", "value" )); user1.setResources(List.of(resource1)); - when(dbaasAdapter.ensureUser("username", null, newName, "admin")).thenReturn(user1); + when(dbaasAdapter.ensureUser(null, null, newName, "admin")).thenReturn(user1); DbResource resource2 = new DbResource(); resource2.setId(UUID.randomUUID()); resource2.setKind("kind"); - resource2.setName("name"); + resource2.setName("newName"); EnsuredUser user2 = new EnsuredUser(); user2.setConnectionProperties(Map.of( "key", "value" )); user2.setResources(List.of(resource2)); - when(dbaasAdapter.ensureUser("username", null, newName2, "admin")).thenReturn(user2); + when(dbaasAdapter.ensureUser(null, null, newName2, "admin")).thenReturn(user2); RestoreResponse restoreResponse = dbBackupV2Service.retryRestore(restoreName); dbBackupV2Service.checkRestoresAsync(); @@ -2081,7 +1896,7 @@ void updateAndValidateClassifier() { String namespace = "test1-namespace"; String namespaceMapped = "test1-namespace-mapped"; - List classifiers = getClassifiers(namespace, namespaceMapped); + List classifiers = getClassifiers(namespace, namespaceMapped); Mapping mapping = new Mapping(); mapping.setNamespaces(Map.of(namespace, namespaceMapped)); @@ -2096,13 +1911,13 @@ void updateClassifier_withoutMapping() { oldClassifier.put(TENANT_ID, "tenant"); oldClassifier.put(SCOPE, "tenant"); - Classifier classifier = new Classifier(); + ClassifierDetails classifier = new ClassifierDetails(); classifier.setClassifierBeforeMapper(oldClassifier); - List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), null); + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), null); assertEquals(1, updatedClassifiers.size()); - Classifier updatedClassifier = updatedClassifiers.getFirst(); + ClassifierDetails updatedClassifier = updatedClassifiers.getFirst(); assertEquals(updatedClassifier.getClassifier(), updatedClassifier.getClassifierBeforeMapper()); assertEquals(updatedClassifier.getClassifierBeforeMapper(), oldClassifier); } @@ -2118,7 +1933,7 @@ void updateClassifier_tenantMapping() { oldClassifier.put(TENANT_ID, tenant); oldClassifier.put(SCOPE, tenant); - Classifier classifier = new Classifier(); + ClassifierDetails classifier = new ClassifierDetails(); classifier.setClassifierBeforeMapper(oldClassifier); Mapping mapping = new Mapping(); @@ -2126,9 +1941,9 @@ void updateClassifier_tenantMapping() { tenant, mappedTenant )); - List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); assertEquals(1, updatedClassifiers.size()); - Classifier updatedClassifier = updatedClassifiers.getFirst(); + ClassifierDetails updatedClassifier = updatedClassifiers.getFirst(); SortedMap mappedClassifier = updatedClassifier.getClassifier(); assertNotNull(mappedClassifier); @@ -2150,7 +1965,7 @@ void updateClassifier_nullTenantMapping() { oldClassifier.put(MICROSERVICE_NAME, "microserviceName"); oldClassifier.put(SCOPE, "service"); - Classifier classifier = new Classifier(); + ClassifierDetails classifier = new ClassifierDetails(); classifier.setClassifierBeforeMapper(oldClassifier); Mapping mapping = new Mapping(); @@ -2158,10 +1973,10 @@ void updateClassifier_nullTenantMapping() { tenant, mappedTenant )); - List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); assertEquals(1, updatedClassifiers.size()); - Classifier updatedClassifier = updatedClassifiers.getFirst(); + ClassifierDetails updatedClassifier = updatedClassifiers.getFirst(); assertEquals(updatedClassifier.getClassifier(), updatedClassifier.getClassifierBeforeMapper()); assertEquals(updatedClassifier.getClassifierBeforeMapper(), oldClassifier); } @@ -2176,7 +1991,7 @@ void updateClassifier_namespaceMapping() { oldClassifier.put(MICROSERVICE_NAME, "microserviceName"); oldClassifier.put(SCOPE, "service"); - Classifier classifier = new Classifier(); + ClassifierDetails classifier = new ClassifierDetails(); classifier.setClassifierBeforeMapper(oldClassifier); Mapping mapping = new Mapping(); @@ -2184,7 +1999,7 @@ void updateClassifier_namespaceMapping() { namespace, mappedNamespace )); - List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); + List updatedClassifiers = dbBackupV2Service.applyMapping(List.of(classifier), mapping); assertEquals(1, updatedClassifiers.size()); SortedMap mappedClassifier = updatedClassifiers.getFirst().getClassifier(); @@ -2196,7 +2011,7 @@ void updateClassifier_namespaceMapping() { assertEquals(mappedNamespace, mappedClassifier.get(NAMESPACE)); } - private static @NotNull List getClassifiers(String namespace, String namespaceMapped) { + private static @NotNull List getClassifiers(String namespace, String namespaceMapped) { SortedMap classifier1 = new TreeMap<>(); classifier1.put("microserviceName", "test1"); classifier1.put("namespace", namespace); @@ -2207,10 +2022,10 @@ void updateClassifier_namespaceMapping() { classifier2.put("namespace", namespaceMapped); classifier2.put("scope", "service"); - Classifier classifierWrapper1 = new Classifier(); + ClassifierDetails classifierWrapper1 = new ClassifierDetails(); classifierWrapper1.setClassifierBeforeMapper(classifier1); - Classifier classifierWrapper2 = new Classifier(); + ClassifierDetails classifierWrapper2 = new ClassifierDetails(); classifierWrapper2.setClassifierBeforeMapper(classifier2); return List.of(classifierWrapper1, classifierWrapper2); @@ -2482,61 +2297,6 @@ void getAllDbByFilter_4() { }); } - @Test - void getAllDbByFilter_5() { - String namespace1 = "namespace1"; - String namespace2 = "namespace2"; - String namespace3 = "namespace3"; - String namespace4 = "namespace4"; - - String microserviceName1 = "microserviceName1"; - String microserviceName2 = "microserviceName2"; - String microserviceName3 = "microserviceName3"; - String microserviceName4 = "microserviceName4"; - String microserviceName5 = "microserviceName5"; - String microserviceName6 = "microserviceName6"; - - String postgresqlType = "postgresql"; - String arangoDbType = "arangodb"; - String cassandraType = "cassandra"; - String adapterId = "adapterId"; - - String dbName1 = "db1"; - String dbName2 = "db2"; - String dbName3 = "db3"; - String dbName4 = "db4"; - String dbName5 = "db5"; - - Database db1 = getDatabase(adapterId, dbName1, false, false, ""); - Database db2 = getDatabase(adapterId, dbName2, false, false, "cfg"); - Database db3 = getDatabase(adapterId, dbName3, false, false, "cfg"); - Database db4 = getDatabase(adapterId, dbName4, false, false, ""); - Database db5 = getDatabase(adapterId, dbName5, false, false, ""); - - DatabaseRegistry registry1 = getDatabaseRegistry(db1, namespace1, microserviceName1, "", postgresqlType); - DatabaseRegistry registry2 = getDatabaseRegistry(db1, namespace1, microserviceName2, "", postgresqlType); - DatabaseRegistry registry3 = getDatabaseRegistry(db2, namespace2, microserviceName3, "", cassandraType); - DatabaseRegistry registry4 = getDatabaseRegistry(db3, namespace2, microserviceName4, "", cassandraType); - DatabaseRegistry registry5 = getDatabaseRegistry(db4, namespace3, microserviceName5, "", arangoDbType); - DatabaseRegistry registry6 = getDatabaseRegistry(db5, namespace4, microserviceName6, "", arangoDbType); - - Stream.of(registry1, registry2, registry3, registry4, registry5, registry6) - .forEach(databaseRegistryDbaasRepository::saveAnyTypeLogDb); - - Filter filter = new Filter(); - filter.setDatabaseKind(List.of(DatabaseKind.TRANSACTIONAL, DatabaseKind.CONFIGURATION)); - - Filter exclude = new Filter(); - exclude.setDatabaseKind(List.of(DatabaseKind.CONFIGURATION, DatabaseKind.TRANSACTIONAL, DatabaseKind.TRANSACTIONAL)); //To check distinct method - - FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); - filterCriteria.setExclude(List.of(exclude)); - - assertThrows(DbNotFoundException.class, - () -> dbBackupV2Service.getAllDbByFilter(filterCriteria)); - } - @Test void getAllDbByFilter_whenDatabasesNotFound() { Filter filter = new Filter(); @@ -2606,11 +2366,11 @@ void getAllDbByFilter_RestorePart_1() { filterCriteria.setExclude(List.of(exclude)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(1, filteredDatabases.size()); - BackupDatabaseDelegate backupDatabaseDelegate = filteredDatabases.getFirst(); + BackupWithClassifiers backupDatabaseDelegate = filteredDatabases.getFirst(); assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase1); assertEquals(backupDatabaseDelegate.classifiers().getFirst().getClassifierBeforeMapper(), backupDatabase1.getClassifiers().getFirst()); @@ -2678,11 +2438,11 @@ void getAllDbByFilter_RestorePart_2() { filterCriteria.setExclude(List.of(exclude)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(1, filteredDatabases.size()); - BackupDatabaseDelegate backupDatabaseDelegate = filteredDatabases.getFirst(); + BackupWithClassifiers backupDatabaseDelegate = filteredDatabases.getFirst(); assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase3); assertEquals(backupDatabaseDelegate.classifiers().getFirst().getClassifierBeforeMapper(), backupDatabase3.getClassifiers().getFirst()); @@ -2728,11 +2488,11 @@ void getAllDbByFilter_RestorePart_3() { filterCriteria.setFilter(List.of(filter1, filter2)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(2, filteredDatabases.size()); - BackupDatabaseDelegate backupDatabaseDelegate1 = filteredDatabases.stream() + BackupWithClassifiers backupDatabaseDelegate1 = filteredDatabases.stream() .filter(db -> dbName1.equals(db.backupDatabase().getName())) .findAny().orElse(null); assertNotNull(backupDatabaseDelegate1); @@ -2748,7 +2508,7 @@ void getAllDbByFilter_RestorePart_3() { assertNotNull(classifier1); assertNotNull(classifier2); - BackupDatabaseDelegate backupDatabaseDelegate2 = filteredDatabases.stream() + BackupWithClassifiers backupDatabaseDelegate2 = filteredDatabases.stream() .filter(db -> dbName2.equals(db.backupDatabase().getName())) .findAny().orElse(null); assertNotNull(backupDatabaseDelegate2); @@ -2794,11 +2554,11 @@ void getAllDbByFilter_RestorePart_4() { filterCriteria.setFilter(List.of(filter1)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(1, filteredDatabases.size()); - BackupDatabaseDelegate backupDatabaseDelegate1 = filteredDatabases.stream() + BackupWithClassifiers backupDatabaseDelegate1 = filteredDatabases.stream() .filter(db -> dbName1.equals(db.backupDatabase().getName())) .findAny().orElse(null); assertNotNull(backupDatabaseDelegate1); @@ -2965,7 +2725,7 @@ void validateAndFilterDatabasesForBackup_whenStrategyFail() { void checkBackupsAsync_shouldNotRunInParallelAcrossNodes() throws Exception { ExecutorService executor = Executors.newFixedThreadPool(2); - when(backupRepository.findBackupsToAggregate()) + when(backupRepository.findBackupsToTrack()) .thenAnswer(new AnswersWithDelay(100, new Returns(List.of()))); Mockito.clearInvocations(backupRepository); @@ -2982,7 +2742,7 @@ void checkBackupsAsync_shouldNotRunInParallelAcrossNodes() throws Exception { executor.shutdown(); executor.awaitTermination(1, SECONDS); - Mockito.verify(backupRepository, Mockito.times(1)).findBackupsToAggregate(); + Mockito.verify(backupRepository, Mockito.times(1)).findBackupsToTrack(); Mockito.reset(backupRepository); } @@ -3086,7 +2846,7 @@ void trackAndAggregate_backupAttemptExceeded_aggregatorMustBeFailed() { @Test void aggregateStatus_shouldReturnInProgress_whenInputInProgressFailedCompleted() { Set statusSet = Set.of(BackupTaskStatus.IN_PROGRESS, BackupTaskStatus.FAILED, BackupTaskStatus.COMPLETED); - BackupStatus backupStatus = dbBackupV2Service.aggregateBackupStatus(statusSet); + BackupStatus backupStatus = dbBackupV2Service.aggregateBackupTaskStatus(statusSet); assertNotNull(backupStatus); assertEquals(BackupStatus.IN_PROGRESS, backupStatus); @@ -3095,7 +2855,7 @@ void aggregateStatus_shouldReturnInProgress_whenInputInProgressFailedCompleted() @Test void aggregateStatus_shouldReturnInProgress_whenInputNotStartedFailedCompleted() { Set statusSet = Set.of(BackupTaskStatus.NOT_STARTED, BackupTaskStatus.FAILED, BackupTaskStatus.COMPLETED); - BackupStatus backupStatus = dbBackupV2Service.aggregateBackupStatus(statusSet); + BackupStatus backupStatus = dbBackupV2Service.aggregateBackupTaskStatus(statusSet); assertNotNull(backupStatus); assertEquals(BackupStatus.IN_PROGRESS, backupStatus); @@ -3104,7 +2864,7 @@ void aggregateStatus_shouldReturnInProgress_whenInputNotStartedFailedCompleted() @Test void aggregateStatus_shouldReturnFailed_whenInputFailedCompleted() { Set statusSet = Set.of(BackupTaskStatus.FAILED, BackupTaskStatus.COMPLETED); - BackupStatus backupStatus = dbBackupV2Service.aggregateBackupStatus(statusSet); + BackupStatus backupStatus = dbBackupV2Service.aggregateBackupTaskStatus(statusSet); assertNotNull(backupStatus); assertEquals(BackupStatus.FAILED, backupStatus); @@ -3113,7 +2873,7 @@ void aggregateStatus_shouldReturnFailed_whenInputFailedCompleted() { @Test void aggregateStatus_shouldReturnCompleted_whenInputCompleted() { Set statusSet = Set.of(BackupTaskStatus.COMPLETED); - BackupStatus backupStatus = dbBackupV2Service.aggregateBackupStatus(statusSet); + BackupStatus backupStatus = dbBackupV2Service.aggregateBackupTaskStatus(statusSet); assertNotNull(backupStatus); assertEquals(BackupStatus.COMPLETED, backupStatus); @@ -3485,6 +3245,24 @@ void deleteRestore_whenRestoreStatusUnprocessable() { () -> dbBackupV2Service.deleteRestore(restoreName)); } + @Test + void deleteBackup() { + String backupName = "backupName"; + String namespace = "namespace"; + Backup backup = getBackup(backupName, namespace); + backupRepository.save(backup); + + Backup existedBackup = backupRepository.findById(backupName); + assertNotNull(existedBackup); + + when(dbaaSHelper.isProductionMode()).thenReturn(false); + + dbBackupV2Service.deleteBackup(backupName); + + Backup deletedBackup = backupRepository.findById(backupName); + assertNull(deletedBackup); + } + private Database getDatabase(String adapterId, String name, boolean isExternal, boolean isBackupDisabled, String bgVersion) { DbState dbState = new DbState(); dbState.setId(UUID.randomUUID()); @@ -3495,7 +3273,7 @@ private Database getDatabase(String adapterId, String name, boolean isExternal, Map map = new HashMap<>(); map.put("role", "admin"); - map.put("username", "username"); + map.put("username", "oldUsername"); Database database = new Database(); database.setId(UUID.randomUUID()); database.setAdapterId(adapterId); @@ -3537,10 +3315,10 @@ private SortedMap getClassifier(String namespace, String microse return classifier; } - private Classifier getClassifier(ClassifierType classifierType, String namespace, String microserviceName, String tenantId, String namespaceBeforeMap, String tenantIdBeforeMap) { + private ClassifierDetails getClassifier(ClassifierType classifierType, String namespace, String microserviceName, String tenantId, String namespaceBeforeMap, String tenantIdBeforeMap) { assertFalse(namespaceBeforeMap.isBlank()); - Classifier classifierWrapper = new Classifier(); + ClassifierDetails classifierWrapper = new ClassifierDetails(); classifierWrapper.setType(classifierType); if (namespace != null && !namespace.isBlank()) { @@ -3587,7 +3365,7 @@ private BackupDatabase getBackupDatabase(String dbName, backupDatabase.setName(dbName); backupDatabase.setClassifiers(classifiers); backupDatabase.setSettings(Map.of("setting", "setting")); - backupDatabase.setUsers(List.of(new BackupDatabase.User("username", "admin"))); + backupDatabase.setUsers(List.of(new BackupDatabase.User("oldUsername", "admin"))); backupDatabase.setConfigurational(configurational); backupDatabase.setStatus(status); backupDatabase.setSize(1); @@ -3659,7 +3437,7 @@ private Backup getBackup(String backupName, String namespace) { BackupDatabase backupDatabase = new BackupDatabase(); backupDatabase.setId(UUID.randomUUID()); backupDatabase.setName("db" + i); - backupDatabase.setUsers(List.of(new BackupDatabase.User("username", "role"))); + backupDatabase.setUsers(List.of(new BackupDatabase.User("oldUsername", "role"))); backupDatabase.setStatus(BackupTaskStatus.COMPLETED); backupDatabase.setPath("path"); backupDatabase.setClassifiers(List.of(classifier)); @@ -3710,7 +3488,7 @@ private Restore getRestore(String restoreName, String namespace) { SortedMap classifier = new TreeMap<>(); classifier.put("namespace", namespace); - Classifier classifierMapper = new Classifier(); + ClassifierDetails classifierMapper = new ClassifierDetails(); classifierMapper.setClassifierBeforeMapper(classifier); List restoreDatabases = new ArrayList<>(); @@ -3718,7 +3496,7 @@ private Restore getRestore(String restoreName, String namespace) { RestoreDatabase restoreDatabase = new RestoreDatabase(); restoreDatabase.setId(UUID.randomUUID()); restoreDatabase.setName("db" + i); - restoreDatabase.setUsers(List.of(new RestoreDatabase.User("username", "admin"))); + restoreDatabase.setUsers(List.of(new RestoreDatabase.User("oldUsername", "admin"))); restoreDatabase.setStatus(RestoreTaskStatus.COMPLETED); restoreDatabase.setPath("path"); restoreDatabase.setClassifiers(List.of(classifierMapper)); @@ -3801,7 +3579,7 @@ private Map> getDatabase(Map db private RestoreDatabase getRestoreDatabase(BackupDatabase backupDatabase, String dbName, - List classifiers, + List classifiers, Map settings, String bgVersion, RestoreTaskStatus status, @@ -3813,7 +3591,7 @@ private RestoreDatabase getRestoreDatabase(BackupDatabase backupDatabase, db.setName(dbName); db.setClassifiers(classifiers); db.setSettings(settings); - db.setUsers(List.of(new RestoreDatabase.User("username", "admin"))); + db.setUsers(List.of(new RestoreDatabase.User("oldUsername", "admin"))); db.setBgVersion(bgVersion); db.setStatus(status); db.setDuration(duration); @@ -3871,7 +3649,7 @@ private Restore getRestore(Backup backup, private RestoreExternalDatabase getRestoreExternalDb( String name, String type, - List classifiers + List classifiers ) { RestoreExternalDatabase db = new RestoreExternalDatabase(); db.setId(UUID.randomUUID()); From d14faa0b4e50c8746e70cc6c4d9f5df59bb410d3 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 5 Feb 2026 13:28:57 +0500 Subject: [PATCH 42/46] style: update OpenApi --- docs/OpenAPI.json | 62 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index a7832d4f..62e4d208 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -663,33 +663,65 @@ } } }, - "ClassifierResponse": { + "ClassifierDetailsResponse": { "type": "object", + "required": [ + "type", + "previousDatabase", + "classifier", + "classifierBeforeMapper" + ], + "description": "Classifier details used during restore operation", "properties": { "type": { - "$ref": "#/components/schemas/ClassifierType" + "type": [ + "string", + "object" + ], + "enum": [ + "NEW", + "REPLACED", + "TRANSIENT_REPLACED" + ], + "description": "Type of classifier in restore context" }, "previousDatabase": { - "type": "string" + "type": "string", + "examples": [ + "oldDb" + ], + "description": "Name of the source database. From which the classifier was obtained via backup" }, "classifier": { "type": "object", - "additionalProperties": {} + "examples": [ + [ + { + "namespace": "namespace", + "microserviceName": "microserviceName", + "scope": "service" + } + ] + ], + "additionalProperties": {}, + "description": "Final classifier used to create a database in the target environment. " }, "classifierBeforeMapper": { "type": "object", - "additionalProperties": {} + "examples": [ + [ + { + "namespace": "namespace", + "microserviceName": "microserviceName", + "scope": "service" + } + ] + ], + "additionalProperties": {}, + "description": "Classifier state before applying restore-side mapping. Represents classifier data coming from backup, adapted to restore model but not yet transformed" } } }, - "ClassifierType": { - "type": "string", - "enum": [ - "NEW", - "REPLACED", - "TRANSIENT_REPLACED" - ] - }, "ClassifierWithRolesRequest": { "type": "object", "required": [ @@ -3154,7 +3186,7 @@ ] ], "items": { - "$ref": "#/components/schemas/ClassifierResponse" + "$ref": "#/components/schemas/ClassifierDetailsResponse" }, "description": "List of database classifiers. Each classifier is a sorted map of attributes." }, @@ -3264,7 +3296,7 @@ "classifiers": { "type": "array", "items": { - "$ref": "#/components/schemas/ClassifierResponse" + "$ref": "#/components/schemas/ClassifierDetailsResponse" }, "description": "List of classifier objects describing database attributes." } From b4d300b7981d9d45309b4820b48ed94b1845e155 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 5 Feb 2026 14:23:37 +0500 Subject: [PATCH 43/46] chore: update entity --- .../v3/DatabaseBackupV2Controller.java | 4 +- ...iers.java => DatabaseWithClassifiers.java} | 4 +- .../dbaas/service/DbBackupV2Service.java | 58 ++++++++++--------- .../dbaas/service/DbBackupV2ServiceTest.java | 24 ++++---- 4 files changed, 47 insertions(+), 43 deletions(-) rename dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/{BackupWithClassifiers.java => DatabaseWithClassifiers.java} (60%) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index 72ddaf55..3179dfc5 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -340,7 +340,7 @@ public Response retryRestore(@Parameter(description = "Unique name of the restor } @Operation(summary = "Remove backup", - description = "Deleting a backup by the specified backup name", + description = "Deleting a backup entirely from DB by the specified backup name. Only for internal usage.", hidden = true ) @APIResponses({ @@ -353,7 +353,7 @@ public Response deleteBackup(@Parameter(description = "Unique name of the backup @PathParam("backupName") @NotBlank String backupName ) { - dbBackupV2Service.deleteBackup(backupName); + dbBackupV2Service.deleteBackupFromDb(backupName); return Response.noContent().build(); } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupWithClassifiers.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/DatabaseWithClassifiers.java similarity index 60% rename from dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupWithClassifiers.java rename to dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/DatabaseWithClassifiers.java index 30f738aa..950acbfe 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/BackupWithClassifiers.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/dto/backupV2/DatabaseWithClassifiers.java @@ -6,5 +6,5 @@ import java.util.List; -public record BackupWithClassifiers(BackupDatabase backupDatabase, - List classifiers) {} +public record DatabaseWithClassifiers(BackupDatabase backupDatabase, + List classifiers) {} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index aad11e5c..4b1b0bae 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -367,7 +367,7 @@ private CompletableFuture trackLogicalBackupAsync(LogicalBackup logicalBac private LogicalBackupAdapterResponse executeTrackBackup(LogicalBackup logicalBackup) { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalBackup.getAdapterId()); - if (isNameEmpty(logicalBackup.getLogicalBackupName())) { + if (isLogicalBackupNotStarted(logicalBackup)) { LogicalBackupAdapterResponse response = startLogicalBackup(logicalBackup); refreshLogicalBackupState(logicalBackup, response); } @@ -689,14 +689,14 @@ private RestoreResponse applyDryRunRestore(Backup backup, RestoreRequest restore return mapper.toRestoreResponse(currRestore); } - protected List getAllDbByFilter(List backupDatabasesToFilter, FilterCriteria filterCriteria) { + protected List getAllDbByFilter(List backupDatabasesToFilter, FilterCriteria filterCriteria) { if (isFilterEmpty(filterCriteria)) return backupDatabasesToFilter.stream() .map(db -> - new BackupWithClassifiers( + new DatabaseWithClassifiers( db, db.getClassifiers().stream() - .map(c -> new ClassifierDetails(ClassifierType.NEW, db.getName(), null, new TreeMap<>(c))) + .map(c -> new ClassifierDetails(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList() ) ) @@ -713,14 +713,14 @@ protected List getAllDbByFilter(List back return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) - .map(c -> new ClassifierDetails(ClassifierType.NEW, db.getName(), null, new TreeMap<>(c))) + .map(c -> new ClassifierDetails(ClassifierType.NEW, null, null, new TreeMap<>(c))) .toList(); if (filteredClassifiers.isEmpty()) { return null; } - return new BackupWithClassifiers(db, filteredClassifiers); + return new DatabaseWithClassifiers(db, filteredClassifiers); }) .filter(Objects::nonNull) .toList(); @@ -737,7 +737,7 @@ protected Restore initializeFullRestoreStructure( restoreRequest.getFilterCriteria()); // Filter internal database classifiers - List filteredBackupDatabases = getAllDbByFilter( + List filteredBackupDatabases = getAllDbByFilter( backup.getLogicalBackups().stream() .flatMap(logicalBackup -> logicalBackup.getBackupDatabases().stream()) .toList(), @@ -751,16 +751,16 @@ protected Restore initializeFullRestoreStructure( // Mapping classifiers List mappedExternalDbs = executeMappingForExternalDb(filteredExternalDbs, restoreRequest.getMapping()); - List mappedBackupDatabases = + List mappedBackupDatabases = applyMappingToBackupDatabases(filteredBackupDatabases, restoreRequest.getMapping()); checkForCollision(mappedExternalDbs, mappedBackupDatabases); // Enrich classifiers - List enrichedBackupClassifiers = enrichInternalDbClassifiers(mappedBackupDatabases); + List enrichedBackupClassifiers = enrichInternalDbClassifiers(mappedBackupDatabases); List enrichedExternalDbs = enrichExternalDbClassifiers(mappedExternalDbs); // Group BackupDatabases by updated adapter and logicalBackupName - Map> groupedByTypeAndAdapter = + Map> groupedByTypeAndAdapter = groupBackupDatabasesByLogicalBackupNameAndAdapter(enrichedBackupClassifiers); log.info("Initializing restore structure: restoreName={}, backupName={}", @@ -810,10 +810,10 @@ protected Restore initializeFullRestoreStructure( return restore; } - private List applyMappingToBackupDatabases(List backupDatabases, Mapping mapping) { + private List applyMappingToBackupDatabases(List backupDatabases, Mapping mapping) { return backupDatabases.stream() .map(db -> - new BackupWithClassifiers(db.backupDatabase(), applyMapping(db.classifiers(), mapping)) + new DatabaseWithClassifiers(db.backupDatabase(), applyMapping(db.classifiers(), mapping)) ).toList(); } @@ -836,12 +836,12 @@ protected List applyMapping(List classifie return classifiers; } - private List enrichInternalDbClassifiers(List backupDatabases) { + private List enrichInternalDbClassifiers(List backupDatabases) { return backupDatabases.stream() .map(db -> { String type = db.backupDatabase().getLogicalBackup().getType(); List updatedClassifiers = findSimilarDbByClassifier(db.classifiers(), type).stream().toList(); - return new BackupWithClassifiers(db.backupDatabase(), updatedClassifiers); + return new DatabaseWithClassifiers(db.backupDatabase(), updatedClassifiers); }).toList(); } @@ -857,7 +857,7 @@ private List enrichExternalDbClassifiers(List externalDatabases, List backupDatabases) { + private void checkForCollision(List externalDatabases, List backupDatabases) { Set> uniqueClassifiers = new HashSet<>(); List duplicateClassifiers = new ArrayList<>(); @@ -971,7 +971,7 @@ private List executeMappingForExternalDb( } private List createRestoreDatabases( - List backupDatabases + List backupDatabases ) { return backupDatabases.stream() .map(delegatedBackupDatabase -> { @@ -1010,8 +1010,8 @@ private String applyMapping(Map map, String oldValue) { return map.getOrDefault(oldValue, oldValue); } - private Map> groupBackupDatabasesByLogicalBackupNameAndAdapter( - List backupDatabases + private Map> groupBackupDatabasesByLogicalBackupNameAndAdapter( + List backupDatabases ) { return backupDatabases.stream() .map(this::mapToAdapterBackupKeyEntry) @@ -1021,8 +1021,8 @@ private Map> groupBackupDatabasesB )); } - private Map.Entry mapToAdapterBackupKeyEntry( - BackupWithClassifiers backupDatabaseDelegate + private Map.Entry mapToAdapterBackupKeyEntry( + DatabaseWithClassifiers backupDatabaseDelegate ) { List classifiers = backupDatabaseDelegate.classifiers(); String type = backupDatabaseDelegate.backupDatabase().getLogicalBackup().getType(); @@ -1048,7 +1048,7 @@ private Map.Entry mapToAdapterBackupKey } return Map.entry(new AdapterBackupKey(adapterId, logicalBackupName), - new BackupWithClassifiers(backupDatabaseDelegate.backupDatabase(), classifiers)); + new DatabaseWithClassifiers(backupDatabaseDelegate.backupDatabase(), classifiers)); } protected void startRestore(Restore restore, boolean dryRun) { @@ -1269,9 +1269,9 @@ protected List ensureUsers(String adapterId, return ensuredUser; }); } catch (Exception e) { - log.error("Failed to ensure user in database [{}]", dbId); + log.error("Failed to ensure user in database [{}] with role [{}]", dbId, user.getRole()); throw new BackupExecutionException( - String.format("Failed to ensure user in database [%s]", dbId), e); + String.format("Failed to ensure user in database [%s] with role [%s]", dbId, user.getRole()), e); } }) .toList(); @@ -1311,7 +1311,7 @@ private void fetchStatuses(Restore restore) { asyncOperations.wrapWithContext( () -> Failsafe.with(retryPolicy).get(() -> { DbaasAdapter adapter = physicalDatabasesService.getAdapterById(logicalRestore.getAdapterId()); - if (isNameEmpty(logicalRestore.getLogicalRestoreName())) { + if (isLogicalRestoreNotStarted(logicalRestore)) { LogicalRestoreAdapterResponse response = logicalRestore(logicalRestore, restore.getStorageName(), restore.getBlobPath(), false); refreshLogicalRestoreState(logicalRestore, response); } @@ -1672,7 +1672,7 @@ protected Map> validateAndFilterDatabasesForBac } @Transactional - public void deleteBackup(String backupName) { + public void deleteBackupFromDb(String backupName) { if (dbaaSHelper.isProductionMode()) { throw new ForbiddenDeleteOperationException(); } @@ -1764,8 +1764,12 @@ private boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } - private boolean isNameEmpty(String name) { - return name == null || name.isBlank(); + private boolean isLogicalBackupNotStarted(LogicalBackup logicalBackup) { + return logicalBackup.getLogicalBackupName() == null || logicalBackup.getLogicalBackupName().isBlank(); + } + + private boolean isLogicalRestoreNotStarted(LogicalRestore logicalRestore) { + return logicalRestore.getLogicalRestoreName() == null || logicalRestore.getLogicalRestoreName().isBlank(); } protected BackupStatus aggregateBackupTaskStatus(Set backupTaskStatuses) { diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index dda4babc..4daf1002 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -2,7 +2,7 @@ import com.netcracker.cloud.dbaas.dto.EnsuredUser; import com.netcracker.cloud.dbaas.dto.backupV2.*; -import com.netcracker.cloud.dbaas.entity.dto.backupV2.BackupWithClassifiers; +import com.netcracker.cloud.dbaas.entity.dto.backupV2.DatabaseWithClassifiers; import com.netcracker.cloud.dbaas.entity.dto.backupV2.LogicalBackupAdapterResponse; import com.netcracker.cloud.dbaas.entity.dto.backupV2.LogicalRestoreAdapterResponse; import com.netcracker.cloud.dbaas.entity.pg.*; @@ -2366,11 +2366,11 @@ void getAllDbByFilter_RestorePart_1() { filterCriteria.setExclude(List.of(exclude)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(1, filteredDatabases.size()); - BackupWithClassifiers backupDatabaseDelegate = filteredDatabases.getFirst(); + DatabaseWithClassifiers backupDatabaseDelegate = filteredDatabases.getFirst(); assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase1); assertEquals(backupDatabaseDelegate.classifiers().getFirst().getClassifierBeforeMapper(), backupDatabase1.getClassifiers().getFirst()); @@ -2438,11 +2438,11 @@ void getAllDbByFilter_RestorePart_2() { filterCriteria.setExclude(List.of(exclude)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(1, filteredDatabases.size()); - BackupWithClassifiers backupDatabaseDelegate = filteredDatabases.getFirst(); + DatabaseWithClassifiers backupDatabaseDelegate = filteredDatabases.getFirst(); assertEquals(backupDatabaseDelegate.backupDatabase(), backupDatabase3); assertEquals(backupDatabaseDelegate.classifiers().getFirst().getClassifierBeforeMapper(), backupDatabase3.getClassifiers().getFirst()); @@ -2488,11 +2488,11 @@ void getAllDbByFilter_RestorePart_3() { filterCriteria.setFilter(List.of(filter1, filter2)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(2, filteredDatabases.size()); - BackupWithClassifiers backupDatabaseDelegate1 = filteredDatabases.stream() + DatabaseWithClassifiers backupDatabaseDelegate1 = filteredDatabases.stream() .filter(db -> dbName1.equals(db.backupDatabase().getName())) .findAny().orElse(null); assertNotNull(backupDatabaseDelegate1); @@ -2508,7 +2508,7 @@ void getAllDbByFilter_RestorePart_3() { assertNotNull(classifier1); assertNotNull(classifier2); - BackupWithClassifiers backupDatabaseDelegate2 = filteredDatabases.stream() + DatabaseWithClassifiers backupDatabaseDelegate2 = filteredDatabases.stream() .filter(db -> dbName2.equals(db.backupDatabase().getName())) .findAny().orElse(null); assertNotNull(backupDatabaseDelegate2); @@ -2554,11 +2554,11 @@ void getAllDbByFilter_RestorePart_4() { filterCriteria.setFilter(List.of(filter1)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); - List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); + List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); assertEquals(1, filteredDatabases.size()); - BackupWithClassifiers backupDatabaseDelegate1 = filteredDatabases.stream() + DatabaseWithClassifiers backupDatabaseDelegate1 = filteredDatabases.stream() .filter(db -> dbName1.equals(db.backupDatabase().getName())) .findAny().orElse(null); assertNotNull(backupDatabaseDelegate1); @@ -3246,7 +3246,7 @@ void deleteRestore_whenRestoreStatusUnprocessable() { } @Test - void deleteBackup() { + void deleteBackupFromDb() { String backupName = "backupName"; String namespace = "namespace"; Backup backup = getBackup(backupName, namespace); @@ -3257,7 +3257,7 @@ void deleteBackup() { when(dbaaSHelper.isProductionMode()).thenReturn(false); - dbBackupV2Service.deleteBackup(backupName); + dbBackupV2Service.deleteBackupFromDb(backupName); Backup deletedBackup = backupRepository.findById(backupName); assertNull(deletedBackup); From 948be7150bd85731152fcf1491d389c37b5ea623 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Thu, 5 Feb 2026 14:52:48 +0500 Subject: [PATCH 44/46] refactor: simplify first classifier selection --- .../netcracker/cloud/dbaas/service/DbBackupV2Service.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 4b1b0bae..36ba13bb 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -1028,11 +1028,7 @@ private Map.Entry mapToAdapterBackupK String type = backupDatabaseDelegate.backupDatabase().getLogicalBackup().getType(); String logicalBackupName = backupDatabaseDelegate.backupDatabase().getLogicalBackup().getLogicalBackupName(); - SortedMap firstNewClassifier = classifiers.stream() - .map(ClassifierDetails::getClassifier) - .filter(Objects::nonNull) - .findFirst() - .orElseGet(() -> classifiers.getFirst().getClassifier()); + SortedMap firstNewClassifier = classifiers.getFirst().getClassifier(); String targetNamespace = (String) firstNewClassifier.get(NAMESPACE); String microserviceName = (String) firstNewClassifier.get(MICROSERVICE_NAME); PhysicalDatabase physicalDatabase = balancingRulesService From 2f45197d892743cdf76cd2cec379c2604e9dd97c Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 6 Feb 2026 14:08:32 +0500 Subject: [PATCH 45/46] feat: add new api to internal usage --- .../v3/DatabaseBackupV2Controller.java | 77 ++++++++++++++++--- .../backupV2/ClassifierDetailsResponse.java | 25 +++--- .../dbaas/dto/backupV2/FilterCriteria.java | 4 +- .../pg/backupV2/FilterCriteriaEntity.java | 2 +- .../dbaas/service/DbBackupV2Service.java | 59 +++++++------- .../v3/DatabaseBackupV2ControllerTest.java | 18 ++--- .../dbaas/service/DbBackupV2ServiceTest.java | 56 +++++++------- docs/OpenAPI.json | 56 +++++++------- 8 files changed, 180 insertions(+), 117 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java index 3179dfc5..8a1a7f12 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2Controller.java @@ -5,8 +5,10 @@ import com.netcracker.cloud.dbaas.dto.backupV2.*; import com.netcracker.cloud.dbaas.enums.BackupStatus; import com.netcracker.cloud.dbaas.enums.RestoreStatus; +import com.netcracker.cloud.dbaas.exceptions.ForbiddenDeleteOperationException; import com.netcracker.cloud.dbaas.exceptions.IntegrityViolationException; import com.netcracker.cloud.dbaas.service.DbBackupV2Service; +import com.netcracker.cloud.dbaas.service.DbaaSHelper; import com.netcracker.cloud.dbaas.utils.DigestUtil; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; @@ -41,10 +43,12 @@ public class DatabaseBackupV2Controller { private final DbBackupV2Service dbBackupV2Service; + private final DbaaSHelper dbaaSHelper; @Inject - public DatabaseBackupV2Controller(DbBackupV2Service dbBackupV2Service) { + public DatabaseBackupV2Controller(DbBackupV2Service dbBackupV2Service, DbaaSHelper dbaaSHelper) { this.dbBackupV2Service = dbBackupV2Service; + this.dbaaSHelper = dbaaSHelper; } @Operation(summary = "Initiate database backup", @@ -72,6 +76,7 @@ public DatabaseBackupV2Controller(DbBackupV2Service dbBackupV2Service) { @POST public Response initiateBackup(@RequestBody(description = "Backup request") @Valid BackupRequest backupRequest, @QueryParam("dryRun") @DefaultValue("false") boolean dryRun) { + log.info("Request to start backup with backup request {}, with dryRun mode {}", backupRequest, dryRun); BackupResponse response = dbBackupV2Service.backup(backupRequest, dryRun); BackupStatus status = response.getStatus(); if (status == BackupStatus.COMPLETED || status == BackupStatus.FAILED) @@ -97,6 +102,7 @@ public Response initiateBackup(@RequestBody(description = "Backup request") @Val public Response getBackup(@Parameter(description = "Unique name of the backup", required = true) @PathParam("backupName") @NotBlank String backupName) { + log.info("Request to get backup {}", backupName); return Response.ok(dbBackupV2Service.getBackup(backupName)).build(); } @@ -118,6 +124,7 @@ public Response getBackup(@Parameter(description = "Unique name of the backup", public Response deleteBackup(@Parameter(description = "Unique name of the backup", required = true) @PathParam("backupName") @NotBlank String backupName, @QueryParam("force") @DefaultValue("false") boolean force) { + log.info("Request to delete backup {} with flag force {}", backupName, force); dbBackupV2Service.deleteBackup(backupName, force); if (force) return Response.accepted().build(); @@ -142,6 +149,7 @@ public Response deleteBackup(@Parameter(description = "Unique name of the backup public Response getBackupStatus(@Parameter(description = "Unique name of the backup", required = true) @PathParam("backupName") @NotBlank String backupName) { + log.info("Request to get backup status {}", backupName); return Response.ok(dbBackupV2Service.getCurrentStatus(backupName)).build(); } @@ -177,6 +185,7 @@ public Response getBackupStatus(@Parameter(description = "Unique name of the bac public Response getBackupMetadata(@Parameter(description = "Unique name of the backup", required = true) @PathParam("backupName") @NotBlank String backupName) { + log.info("Request to get backup metadata {}", backupName); BackupResponse response = dbBackupV2Service.getBackupMetadata(backupName); String digestHeader = DigestUtil.calculateDigest(response); return Response.ok(response) @@ -212,6 +221,8 @@ public Response uploadMetadata( @HeaderParam("Digest") @NotNull String digestHeader, @RequestBody(description = "Backup metadata") @Valid BackupResponse backupResponse ) { + log.info("Request to upload backup metadata {}", backupResponse); + log.debug("Backup digest {}", digestHeader); String calculatedDigest = DigestUtil.calculateDigest(backupResponse); if (!calculatedDigest.equals(digestHeader)) throw new IntegrityViolationException( @@ -250,7 +261,32 @@ public Response restoreBackup(@Parameter(description = "Unique name of the backu @RequestBody(description = "Restore request") @Valid RestoreRequest restoreRequest, @QueryParam("dryRun") @DefaultValue("false") boolean dryRun) { - RestoreResponse response = dbBackupV2Service.restore(backupName, restoreRequest, dryRun); + log.info("Request to restore backup {}, restore request {}, dryRun mode {}", backupName, restoreRequest, dryRun); + return restore(backupName, restoreRequest, dryRun, false); + } + + @Operation( + summary = "Restore from backup with parallel execution allowed", + description = "Only for internal usage", + hidden = true + ) + @Path("/backup/{backupName}/restore/allowParallel") + @POST + public Response restoreBackupAllowParallel(@Parameter(description = "Unique name of the backup", required = true) + @PathParam("backupName") @NotBlank String backupName, + @RequestBody(description = "Restore request") + @Valid RestoreRequest restoreRequest, + @QueryParam("dryRun") @DefaultValue("false") boolean dryRun) { + log.info("Request to restore backup with parallel execution allowed," + + " backup name {}, restore request {}, dryRun mode {}", backupName, restoreRequest, dryRun); + if (dbaaSHelper.isProductionMode()) { + throw new ForbiddenDeleteOperationException(); + } + return restore(backupName, restoreRequest, dryRun, true); + } + + private Response restore(String backupName, RestoreRequest restoreRequest, boolean dryRun, boolean allowParallel) { + RestoreResponse response = dbBackupV2Service.restore(backupName, restoreRequest, dryRun, allowParallel); RestoreStatus status = response.getStatus(); if (status == RestoreStatus.COMPLETED || status == RestoreStatus.FAILED) return Response.ok(response).build(); @@ -275,6 +311,7 @@ public Response restoreBackup(@Parameter(description = "Unique name of the backu public Response getRestore(@Parameter(description = "Unique name of the restore operation", required = true) @PathParam("restoreName") @NotBlank String restoreName) { + log.info("Request to get restore {}", restoreName); return Response.ok(dbBackupV2Service.getRestore(restoreName)).build(); } @@ -293,6 +330,7 @@ public Response getRestore(@Parameter(description = "Unique name of the restore public Response deleteRestore(@Parameter(description = "Unique name of the restore operation", required = true) @PathParam("restoreName") @NotBlank String restoreName) { + log.info("Request to delete restore {}", restoreName); dbBackupV2Service.deleteRestore(restoreName); return Response.noContent().build(); } @@ -313,6 +351,7 @@ public Response deleteRestore(@Parameter(description = "Unique name of the resto public Response getRestoreStatus(@Parameter(description = "Unique name of the restore operation", required = true) @PathParam("restoreName") @NotBlank String restoreName) { + log.info("Request to get restore status {}", restoreName); return Response.ok(dbBackupV2Service.getRestoreStatus(restoreName)).build(); } @@ -336,23 +375,41 @@ public Response getRestoreStatus(@Parameter(description = "Unique name of the re public Response retryRestore(@Parameter(description = "Unique name of the restore operation", required = true) @PathParam("restoreName") @NotBlank String restoreName) { - return Response.accepted(dbBackupV2Service.retryRestore(restoreName)).build(); + log.info("Request to retry restore {}", restoreName); + return Response.accepted(dbBackupV2Service.retryRestore(restoreName, false)).build(); + } + + @Operation( + summary = "Retry restore with parallel execution allowed", + description = "Only for internal usage", + hidden = true + ) + @Path("/restore/{restoreName}/retry/allowParallel") + @POST + public Response retryRestoreAllowParallel(@Parameter(description = "Unique name of the restore operation", required = true) + @PathParam("restoreName") + @NotBlank String restoreName) { + log.info("Request to retry restore with parallel execution alllowed, restore name {}", restoreName); + if (dbaaSHelper.isProductionMode()) { + throw new ForbiddenDeleteOperationException(); + } + return Response.accepted(dbBackupV2Service.retryRestore(restoreName, true)).build(); } @Operation(summary = "Remove backup", description = "Deleting a backup entirely from DB by the specified backup name. Only for internal usage.", hidden = true ) - @APIResponses({ - @APIResponse(responseCode = "204", description = "The backup operation deleted successfully", content = @Content(schema = @Schema(implementation = String.class))), - @APIResponse(responseCode = "403", description = "The DBaaS is working in PROD mode. Deleting backup is prohibited", content = @Content(schema = @Schema(implementation = String.class))) - }) @Path("/backup/{backupName}/forceDelete") @DELETE - public Response deleteBackup(@Parameter(description = "Unique name of the backup operation", required = true) - @PathParam("backupName") - @NotBlank String backupName + public Response deleteBackupFromDb(@Parameter(description = "Unique name of the backup operation", required = true) + @PathParam("backupName") + @NotBlank String backupName ) { + log.info("Request to delete backup from db, backup name {}", backupName); + if (dbaaSHelper.isProductionMode()) { + throw new ForbiddenDeleteOperationException(); + } dbBackupV2Service.deleteBackupFromDb(backupName); return Response.noContent().build(); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java index 56946baa..9c801efc 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/ClassifierDetailsResponse.java @@ -19,26 +19,27 @@ public class ClassifierDetailsResponse { implementation = ClassifierType.class ) private ClassifierType type; + @Schema( - description = "Name of the source database. From which the classifier was obtained via backup", - required = true, - examples = { - "oldDb" - } + description = "Name of the existing database previously associated with this classifier," + + " used when the classifier replaces or transiently replaces another database during restore", + nullable = true, + examples = {"dbaas_12345"} ) private String previousDatabase; + @Schema( - description = "Final classifier used to create a database in the target environment. ", - examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]", + description = "Final classifier used to create a database in the target environment.", + examples = "{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}", required = true ) private SortedMap classifier; + @Schema( - description = "Classifier state before applying restore-side mapping. " + - "Represents classifier data coming from backup, adapted to restore model " + - "but not yet transformed", - examples = "[{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}]", - required = true + description = "Original (pre-mapping) classifier from backup database preserved " + + "to track how mapping changed the classifier during restore", + examples = "{\"namespace\":\"namespace\", \"microserviceName\":\"microserviceName\", \"scope\":\"service\"}", + nullable = true ) private SortedMap classifierBeforeMapper; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java index d7004712..58c945d8 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/backupV2/FilterCriteria.java @@ -15,7 +15,7 @@ @NoArgsConstructor @Schema(description = "Group of filters for backup and restore operations. Filters are applied in the following order:\n" + "\n" + - "1. `filter`\n" + + "1. `include`\n" + "2. `exclude`") public class FilterCriteria { @Schema( @@ -24,7 +24,7 @@ public class FilterCriteria { @NotNull(groups = {BackupGroup.class}) @Size(min = 1, groups = {BackupGroup.class}, message = "there should be at least one filter specified") @Valid - private List filter = new ArrayList<>(); + private List include = new ArrayList<>(); @Schema( description = "Exclude databases that match any of the filters in the list" diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java index 3412b59b..74b2e43c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/backupV2/FilterCriteriaEntity.java @@ -6,6 +6,6 @@ @Data public class FilterCriteriaEntity { - private List filter; + private List include; private List exclude; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index 36ba13bb..f4153492 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -77,7 +77,6 @@ public class DbBackupV2Service { private final PasswordEncryption encryption; private final BgNamespaceRepository bgNamespaceRepository; private final LockProvider lockProvider; - private final DbaaSHelper dbaaSHelper; private final Duration retryDelay; private final int retryAttempts; @@ -111,17 +110,15 @@ public DbBackupV2Service(BackupRepository backupRepository, this.encryption = encryption; this.bgNamespaceRepository = bgNamespaceRepository; this.lockProvider = lockProvider; - this.dbaaSHelper = dbaaSHelper; this.retryDelay = retryDelay; this.retryAttempts = retryAttempts; this.retryCount = retryCount; } public BackupResponse backup(BackupRequest backupRequest, boolean dryRun) { - String backupName = backupRequest.getBackupName(); - backupExistenceCheck(backupName); + backupExistenceCheck(backupRequest.getBackupName()); - log.info("Start backup process with name {}", backupName); + log.info("Start backup process with backupRequest {}", backupRequest); Map> filteredDb = validateAndFilterDatabasesForBackup( getAllDbByFilter(backupRequest.getFilterCriteria()), backupRequest.getIgnoreNotBackupableDatabases(), @@ -433,7 +430,7 @@ public BackupStatusResponse getCurrentStatus(String backupName) { protected Map> getAllDbByFilter(FilterCriteria filterCriteria) { List filteredDatabases = databaseRegistryDbaasRepository - .findAllDatabasesByFilter(filterCriteria.getFilter()) + .findAllDatabasesByFilter(filterCriteria.getInclude()) .stream() .filter(registry -> { if (!isValidRegistry(registry)) @@ -648,7 +645,7 @@ private void finalizeBackupDeletion(Backup backup, Map failedAda backupRepository.save(backup); } - public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, boolean dryRun) { + public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, boolean dryRun, boolean allowParallel) { String restoreName = restoreRequest.getRestoreName(); if (restoreRepository.findByIdOptional(restoreName).isPresent()) { log.error("Restore with name {} already exists", restoreName); @@ -666,7 +663,7 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, Restore restore = restoreLockWrapper(() -> { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); return restoreRepository.save(currRestore); - }); + }, allowParallel); // DryRun on adapters startRestore(restore, true); @@ -683,7 +680,6 @@ public RestoreResponse restore(String backupName, RestoreRequest restoreRequest, private RestoreResponse applyDryRunRestore(Backup backup, RestoreRequest restoreRequest) { Restore currRestore = initializeFullRestoreStructure(backup, restoreRequest); - // DryRun on adapters startRestore(currRestore, true); aggregateRestoreStatus(currRestore); return mapper.toRestoreResponse(currRestore); @@ -710,7 +706,7 @@ protected List getAllDbByFilter(List ba String microserviceName = (String) classifier.get(MICROSERVICE_NAME); String type = db.getLogicalBackup().getType(); boolean configurational = db.isConfigurational(); - return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) + return filterCriteria.getInclude().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, configurational)) && filterCriteria.getExclude().stream().noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, configurational)); }) .map(c -> new ClassifierDetails(ClassifierType.NEW, null, null, new TreeMap<>(c))) @@ -918,8 +914,7 @@ protected List validateAndFilterExternalDb( .toList() )) .toList(); - } - else { + } else { restoreExternalDatabases = externalDatabases.stream() .map(db -> { List filteredClassifiers = db.getClassifiers().stream() @@ -927,7 +922,7 @@ protected List validateAndFilterExternalDb( String namespace = (String) classifier.get(NAMESPACE); String microserviceName = (String) classifier.get(MICROSERVICE_NAME); String type = db.getType(); - return filterCriteria.getFilter().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, false)) + return filterCriteria.getInclude().stream().anyMatch(filter -> isMatches(filter, namespace, microserviceName, type, false)) && filterCriteria.getExclude().stream().filter(this::isFilled).noneMatch(ex -> isMatches(ex, namespace, microserviceName, type, false));//isFilled }) .map(c -> new ClassifierDetails(ClassifierType.NEW, db.getName(), null, new TreeMap<>(c))) @@ -1231,7 +1226,7 @@ protected void checkRestoresAsync() { ) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); - initializeLogicalDatabasesFromRestore(restore, dbNameToEnsuredUsers); + createLogicalDatabasesFromRestore(restore, dbNameToEnsuredUsers); restoreRepository.save(restore); } catch (Exception e) { log.error("Exception occurred during restore process", e); @@ -1366,8 +1361,8 @@ private void aggregateRestoreStatus(Restore restore) { } @Transactional - protected void initializeLogicalDatabasesFromRestore(Restore restore, - Map> dbNameToEnsuredUsers) { + protected void createLogicalDatabasesFromRestore(Restore restore, + Map> dbNameToEnsuredUsers) { log.info("Start creating logical databases from restore {}", restore.getName()); // Creating LogicalDb based logicalRestores restore.getLogicalRestores().forEach(logicalRestore -> { @@ -1463,10 +1458,20 @@ private void findAndMarkDatabaseAsOrphan(List classifiers, St SortedMap currClassifier = classifier.getClassifier(); databaseRegistryDbaasRepository .getDatabaseByClassifierAndType(currClassifier, type) - .ifPresent(dbRegistry -> { + .ifPresentOrElse(dbRegistry -> { dBaaService.markDatabasesAsOrphan(dbRegistry); - log.info("Database {} marked as orphan", dbRegistry.getDatabase().getId()); databaseRegistryDbaasRepository.saveAnyTypeLogDb(dbRegistry); + log.info( + "Database marked as orphan: dbId={}, dbType={}, classifier={}", + dbRegistry.getDatabase().getId(), + type, + currClassifier + ); + }, () -> { + log.debug("Database not found for classifier: dbType={}, classifierType={}, classifier={}", + type, + classifier.getType(), + currClassifier); }); }); } @@ -1546,10 +1551,9 @@ public void deleteRestore(String restoreName) { restoreRepository.save(restore); } - public RestoreResponse retryRestore(String restoreName) { - // Check existence of restore by restoreName + public RestoreResponse retryRestore(String restoreName, boolean allowParallel) { Restore restore = getRestoreOrThrowException(restoreName); - // Check if the status of the restor has FAILED + if (RestoreStatus.FAILED != restore.getStatus()) { throw new UnprocessableEntityException( restoreName, @@ -1564,7 +1568,8 @@ public RestoreResponse retryRestore(String restoreName) { retryRestore(restore); aggregateRestoreStatus(restore); return restoreRepository.save(restore); - }); + }, allowParallel); + return mapper.toRestoreResponse(retriedRestore); } @@ -1669,9 +1674,6 @@ protected Map> validateAndFilterDatabasesForBac @Transactional public void deleteBackupFromDb(String backupName) { - if (dbaaSHelper.isProductionMode()) { - throw new ForbiddenDeleteOperationException(); - } backupRepository.deleteById(backupName); } @@ -1696,7 +1698,10 @@ private Restore getRestoreOrThrowException(String restoreName) { .orElseThrow(() -> new BackupRestorationNotFoundException(restoreName, Source.builder().build())); } - private T restoreLockWrapper(Supplier action) { + private T restoreLockWrapper(Supplier action, boolean allowParallel) { + if (allowParallel) + return action.get(); + // Only one restore operation able to process LockConfiguration config = new LockConfiguration( Instant.now(), @@ -1753,7 +1758,7 @@ private String extractErrorMessage(Throwable throwable) { private boolean isFilterEmpty(FilterCriteria filterCriteria) { return filterCriteria == null - || (isEmpty(filterCriteria.getFilter()) && isEmpty(filterCriteria.getExclude())); + || (isEmpty(filterCriteria.getInclude()) && isEmpty(filterCriteria.getExclude())); } private boolean isEmpty(Collection c) { diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java index 343d8fa6..e3606488 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/v3/DatabaseBackupV2ControllerTest.java @@ -83,7 +83,7 @@ void initiateBackup_invalidDto() { .statusCode(BAD_REQUEST.getStatusCode()) .body("message", allOf( containsString("backupName: must not be blank"), - containsString("filter: there should be at least one filter specified") + containsString("include: there should be at least one filter specified") )); verify(dbBackupV2Service, times(0)).backup(any(), anyBoolean()); @@ -143,7 +143,7 @@ void initiateBackup_emptyFilterCase() { BackupRequest backupRequest = createBackupRequest(namespace, backupName); FilterCriteria emptyFilterCriteria = backupRequest.getFilterCriteria(); - emptyFilterCriteria.setFilter(List.of(new Filter())); + emptyFilterCriteria.setInclude(List.of(new Filter())); emptyFilterCriteria.setExclude(List.of(new Filter())); given().auth().preemptive().basic("backup_manager", "backup_manager") @@ -155,7 +155,7 @@ void initiateBackup_emptyFilterCase() { .body("reason", equalTo("Request does not contain required fields")) .body("message", allOf( containsString("exclude[0]: Filter must have at least one non-null field"), - containsString("filter[0]: Filter must have at least one non-null field") + containsString("include[0]: Filter must have at least one non-null field") ) ); verify(dbBackupV2Service, times(0)).backup(backupRequest, false); @@ -169,7 +169,7 @@ void restoreBackup_emptyFilterCase() { RestoreRequest restoreRequest = createRestoreRequest(namespace, restoreName); FilterCriteria emptyFilterCriteria = restoreRequest.getFilterCriteria(); - emptyFilterCriteria.setFilter(List.of(new Filter())); + emptyFilterCriteria.setInclude(List.of(new Filter())); emptyFilterCriteria.setExclude(List.of(new Filter())); given().auth().preemptive().basic("backup_manager", "backup_manager") @@ -182,10 +182,10 @@ void restoreBackup_emptyFilterCase() { .body("reason", equalTo("Request does not contain required fields")) .body("message", allOf( containsString("exclude[0]: Filter must have at least one non-null field"), - containsString("filter[0]: Filter must have at least one non-null field") + containsString("include[0]: Filter must have at least one non-null field") ) ); - verify(dbBackupV2Service, times(0)).restore(backupName, restoreRequest, false); + verify(dbBackupV2Service, times(0)).restore(backupName, restoreRequest, false, false); } @Test @@ -371,7 +371,7 @@ public static BackupRequest createBackupRequest(String namespace, String backupN filter.setNamespace(List.of(namespace)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); BackupRequest dto = new BackupRequest(); dto.setFilterCriteria(filterCriteria); @@ -388,7 +388,7 @@ public static RestoreRequest createRestoreRequest(String namespace, String resto filter.setNamespace(List.of(namespace)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); RestoreRequest dto = new RestoreRequest(); dto.setRestoreName(restoreName); @@ -445,7 +445,7 @@ private BackupResponse createBackupResponse(String backupName) { filter.setNamespace(List.of("namespace")); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); SortedMap map = new TreeMap<>(); map.put("key", "value"); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java index 4daf1002..bad5f8b1 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/DbBackupV2ServiceTest.java @@ -286,8 +286,8 @@ void backup_backupFinishedStatusCompletedExternalDatabaseStrategyInclude() { FilterCriteriaEntity backupFilter = backup.getFilterCriteria(); assertNotNull(backupFilter); - assertEquals(1, backupFilter.getFilter().size()); - assertEquals(namespace, backupFilter.getFilter().getFirst().getNamespace().getFirst()); + assertEquals(1, backupFilter.getInclude().size()); + assertEquals(namespace, backupFilter.getInclude().getFirst().getNamespace().getFirst()); List externalDatabases = backup.getExternalDatabases(); assertNotNull(externalDatabases); @@ -890,7 +890,7 @@ void restore_withoutMapping_finishedWithStatusCompleted() { RestoreResponse restoreResponse = dbBackupV2Service.restore( backupName, getRestoreRequest(restoreName, List.of(namespace), ExternalDatabaseStrategy.FAIL, null, null), - false); + false, false); assertNotNull(restoreResponse); assertEquals(restoreName, restoreResponse.getRestoreName()); @@ -1169,7 +1169,7 @@ void restore_withMapping_finishedWithStatusCompleted() { RestoreResponse restoreResponse = dbBackupV2Service.restore( backupName, getRestoreRequest(restoreName, List.of(namespace), ExternalDatabaseStrategy.INCLUDE, namespaceMap, tenantMap), - false); + false, false); assertNotNull(restoreResponse); assertEquals(restoreName, restoreResponse.getRestoreName()); @@ -1371,7 +1371,7 @@ void restore_mappingInvokeCollision() { IllegalResourceStateException ex = assertThrows(IllegalResourceStateException.class, () -> dbBackupV2Service.restore(backupName, getRestoreRequest(restoreName, List.of(namespace), ExternalDatabaseStrategy.FAIL, mapping.getNamespaces(), mapping.getTenants()), - false + false, false )); String msg = mappedClassifier.toString(); @@ -1487,7 +1487,7 @@ void restore_similarRegistryInAnotherNamespace_shouldCreateNewDb() { dbBackupV2Service.restore(backupName, getRestoreRequest(restoreName, List.of(namespace), ExternalDatabaseStrategy.INCLUDE, namespaceMap, tenantMap), - false + false, false ); dbBackupV2Service.checkRestoresAsync(); @@ -1661,7 +1661,7 @@ void restore_dryRun() { RestoreResponse restoreResponse = dbBackupV2Service.restore(backupName, getRestoreRequest(restoreName, List.of(namespace), ExternalDatabaseStrategy.INCLUDE, namespaceMap, tenantMap), - true + true, false ); assertNull(restoreRepository.findById(restoreName)); assertNotNull(restoreResponse); @@ -1849,7 +1849,7 @@ void retryRestore() { when(dbaasAdapter.ensureUser(null, null, newName2, "admin")).thenReturn(user2); - RestoreResponse restoreResponse = dbBackupV2Service.retryRestore(restoreName); + RestoreResponse restoreResponse = dbBackupV2Service.retryRestore(restoreName, false); dbBackupV2Service.checkRestoresAsync(); assertEquals(restoreName, restoreResponse.getRestoreName()); @@ -2083,7 +2083,7 @@ void getAllDbByFilter_1() { exclude.setMicroserviceName(List.of(microserviceName1)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); filterCriteria.setExclude(List.of(exclude)); Map> dbToBackup = dbBackupV2Service.getAllDbByFilter(filterCriteria); @@ -2154,7 +2154,7 @@ void getAllDbByFilter_2() { exclude2.setMicroserviceName(List.of(microserviceName4)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter1, filter2)); + filterCriteria.setInclude(List.of(filter1, filter2)); filterCriteria.setExclude(List.of(exclude, exclude2)); Map> dbToBackup = dbBackupV2Service.getAllDbByFilter(filterCriteria); @@ -2213,7 +2213,7 @@ void getAllDbByFilter_3() { filter.setMicroserviceName(List.of(microserviceName1)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); Map> filteredDbs = dbBackupV2Service.getAllDbByFilter(filterCriteria); assertEquals(1, filteredDbs.size()); @@ -2285,7 +2285,7 @@ void getAllDbByFilter_4() { filter1.setNamespace(List.of(namespace1)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter, filter1)); + filterCriteria.setInclude(List.of(filter, filter1)); Map> allDbByFilter = dbBackupV2Service.getAllDbByFilter(filterCriteria); @@ -2302,7 +2302,7 @@ void getAllDbByFilter_whenDatabasesNotFound() { Filter filter = new Filter(); filter.setNamespace(List.of("namespace")); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); assertThrows(DbNotFoundException.class, () -> dbBackupV2Service.getAllDbByFilter(filterCriteria)); @@ -2362,7 +2362,7 @@ void getAllDbByFilter_RestorePart_1() { exclude.setMicroserviceName(List.of(microserviceName4)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); filterCriteria.setExclude(List.of(exclude)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); @@ -2434,7 +2434,7 @@ void getAllDbByFilter_RestorePart_2() { exclude.setDatabaseType(List.of(DatabaseType.POSTGRESQL)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter1, filter2)); + filterCriteria.setInclude(List.of(filter1, filter2)); filterCriteria.setExclude(List.of(exclude)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3, backupDatabase4, backupDatabase5, backupDatabase6); @@ -2485,7 +2485,7 @@ void getAllDbByFilter_RestorePart_3() { filter2.setNamespace(List.of(namespace2)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter1, filter2)); + filterCriteria.setInclude(List.of(filter1, filter2)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); @@ -2551,7 +2551,7 @@ void getAllDbByFilter_RestorePart_4() { filter1.setNamespace(List.of(namespace1)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter1)); + filterCriteria.setInclude(List.of(filter1)); List backupDatabases = List.of(backupDatabase1, backupDatabase2, backupDatabase3); List filteredDatabases = dbBackupV2Service.getAllDbByFilter(backupDatabases, filterCriteria); @@ -2599,7 +2599,7 @@ void validateAndFilterExternalDb_testFiltering() { exclude.setMicroserviceName(List.of(microserviceName2)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); filterCriteria.setExclude(List.of(exclude)); List restoreExternalDatabases = dbBackupV2Service.validateAndFilterExternalDb(List.of(externalDatabase1, externalDatabase2, externalDatabase3), ExternalDatabaseStrategy.INCLUDE, filterCriteria); @@ -2904,8 +2904,8 @@ void getBackup() { FilterCriteria responseFilterCriteria = response.getFilterCriteria(); assertNotNull(responseFilterCriteria); - assertEquals(1, responseFilterCriteria.getFilter().size()); - assertEquals(namespace, responseFilterCriteria.getFilter().getFirst().getNamespace().getFirst()); + assertEquals(1, responseFilterCriteria.getInclude().size()); + assertEquals(namespace, responseFilterCriteria.getInclude().getFirst().getNamespace().getFirst()); List externalDatabases = backup.getExternalDatabases(); assertNull(externalDatabases); @@ -2978,8 +2978,8 @@ void getRestore() { FilterCriteria responseFilterCriteria = response.getFilterCriteria(); assertNotNull(responseFilterCriteria); - assertEquals(1, responseFilterCriteria.getFilter().size()); - assertEquals(namespace, responseFilterCriteria.getFilter().getFirst().getNamespace().getFirst()); + assertEquals(1, responseFilterCriteria.getInclude().size()); + assertEquals(namespace, responseFilterCriteria.getInclude().getFirst().getNamespace().getFirst()); List externalDatabases = restore.getExternalDatabases(); assertNull(externalDatabases); @@ -3470,7 +3470,7 @@ private Backup getBackup(String backupName, String namespace) { filter.setNamespace(List.of(namespace)); FilterCriteriaEntity criteriaEntity = new FilterCriteriaEntity(); - criteriaEntity.setFilter(List.of(filter)); + criteriaEntity.setInclude(List.of(filter)); Backup backup = new Backup(); backup.setName(backupName); @@ -3528,7 +3528,7 @@ private Restore getRestore(String restoreName, String namespace) { FilterEntity filter = new FilterEntity(); filter.setNamespace(List.of(namespace)); FilterCriteriaEntity criteriaEntity = new FilterCriteriaEntity(); - criteriaEntity.setFilter(List.of(filter)); + criteriaEntity.setInclude(List.of(filter)); Restore restore = new Restore(); restore.setName(restoreName); @@ -3668,7 +3668,7 @@ private BackupRequest getBackupRequest(String backupName, filter.setNamespace(namespaces); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); BackupRequest dto = new BackupRequest(); dto.setFilterCriteria(filterCriteria); @@ -3691,7 +3691,7 @@ private RestoreRequest getRestoreRequest( filter.setNamespace(namespaces); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); Mapping mapping = new Mapping(); mapping.setNamespaces(namespaceMapping); @@ -3746,7 +3746,7 @@ private BackupResponse getBackupResponse(String backupName, String namespace) { filter.setNamespace(List.of(namespace)); FilterCriteria filterCriteria = new FilterCriteria(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); SortedMap map = new TreeMap<>(); map.put("key", "value"); @@ -3780,7 +3780,7 @@ private FilterCriteriaEntity getFilterCriteriaEntity(List namespaces) { filter.setNamespace(namespaces); FilterCriteriaEntity filterCriteria = new FilterCriteriaEntity(); - filterCriteria.setFilter(List.of(filter)); + filterCriteria.setInclude(List.of(filter)); return filterCriteria; } } diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index 62e4d208..973f620d 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -354,7 +354,7 @@ "type": "object", "description": "Filter criteria", "properties": { - "filter": { + "include": { "type": "array", "items": { "$ref": "#/components/schemas/Filter" @@ -461,7 +461,7 @@ "type": "object", "description": "Filter criteria", "properties": { - "filter": { + "include": { "type": "array", "items": { "$ref": "#/components/schemas/Filter" @@ -667,9 +667,7 @@ "type": "object", "required": [ "type", - "previousDatabase", - "classifier", - "classifierBeforeMapper" + "classifier" ], "description": "Classifier details used during restore operation", "properties": { @@ -686,39 +684,41 @@ "description": "Type of classifier in restore context" }, "previousDatabase": { - "type": "string", + "type": [ + "string", + "null" + ], "examples": [ - "oldDb" + "dbaas_12345" ], - "description": "Name of the source database. From which the classifier was obtained via backup" + "description": "Name of the existing database previously associated with this classifier, used when the classifier replaces or transiently replaces another database during restore" }, "classifier": { "type": "object", "examples": [ - [ - { - "namespace": "namespace", - "microserviceName": "microserviceName", - "scope": "service" - } - ] + { + "namespace": "namespace", + "microserviceName": "microserviceName", + "scope": "service" + } ], "additionalProperties": {}, - "description": "Final classifier used to create a database in the target environment. " + "description": "Final classifier used to create a database in the target environment." }, "classifierBeforeMapper": { - "type": "object", + "type": [ + "object", + "null" + ], "examples": [ - [ - { - "namespace": "namespace", - "microserviceName": "microserviceName", - "scope": "service" - } - ] + { + "namespace": "namespace", + "microserviceName": "microserviceName", + "scope": "service" + } ], "additionalProperties": {}, - "description": "Classifier state before applying restore-side mapping. Represents classifier data coming from backup, adapted to restore model but not yet transformed" + "description": "Original (pre-mapping) classifier from backup database preserved to track how mapping changed the classifier during restore" } } }, @@ -1860,7 +1860,7 @@ "type": "object", "description": "Filter criteria", "properties": { - "filter": { + "include": { "type": "array", "items": { "$ref": "#/components/schemas/Filter" @@ -3363,7 +3363,7 @@ "type": "object", "description": "Filter criteria", "properties": { - "filter": { + "include": { "type": "array", "items": { "$ref": "#/components/schemas/Filter" @@ -3490,7 +3490,7 @@ "type": "object", "description": "Criteria used to filter restore operations", "properties": { - "filter": { + "include": { "type": "array", "items": { "$ref": "#/components/schemas/Filter" From eacb0a2407d9804ad12b8449990c38830cb85a80 Mon Sep 17 00:00:00 2001 From: Alisher Askar Date: Fri, 6 Feb 2026 16:42:31 +0500 Subject: [PATCH 46/46] chore: remove unused injection --- .../netcracker/cloud/dbaas/service/DbBackupV2Service.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java index f4153492..2863b314 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/DbBackupV2Service.java @@ -94,7 +94,6 @@ public DbBackupV2Service(BackupRepository backupRepository, PasswordEncryption encryption, BgNamespaceRepository bgNamespaceRepository, LockProvider lockProvider, - DbaaSHelper dbaaSHelper, @ConfigProperty(name = "dbaas.backup-restore.retry.delay.seconds") Duration retryDelay, @ConfigProperty(name = "dbaas.backup-restore.retry.attempts") int retryAttempts, @ConfigProperty(name = "dbaas.backup-restore.check.attempts") int retryCount @@ -1467,12 +1466,11 @@ private void findAndMarkDatabaseAsOrphan(List classifiers, St type, currClassifier ); - }, () -> { - log.debug("Database not found for classifier: dbType={}, classifierType={}, classifier={}", + }, () -> log.debug("Database not found for classifier: dbType={}, classifierType={}, classifier={}", type, classifier.getType(), - currClassifier); - }); + currClassifier) + ); }); }