Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
07e49bd
feat: expand filtering
Logaka Dec 2, 2025
9765ff6
refactor: rewrite dynamic SQL filter
Logaka Dec 8, 2025
e0963a5
Merge branch 'main' into feat/backupV2-add-complex-filtering
Logaka Dec 8, 2025
f665ab3
feat: add NotEmptyFilter validation to Filter class
Logaka Dec 9, 2025
af1004a
feat: expanded filtering for external databases
Logaka Dec 9, 2025
5563e44
feat: add dryRun
Logaka Dec 15, 2025
b011b17
Merge branch 'refs/heads/main' into feat/restore-dryRun
Logaka Dec 15, 2025
31e33e7
feat: change restore classifier variable
Logaka Dec 18, 2025
fb32b57
Merge branch 'main' into feat/backupV2-add-complex-filtering
Logaka Dec 18, 2025
6b56a7d
fix: prevent empty filter from generating invalid WHERE clause
Logaka Dec 18, 2025
891c12f
fix: prevent state-changing logic during dry-run in findSimilarDbByCl…
Logaka Dec 19, 2025
9ba3487
test: add tests for dry-run
Logaka Dec 22, 2025
7380508
refactor: reorganize packages
Logaka Dec 22, 2025
d279bd2
refactor: remove attemptCount, duration fields
Logaka Dec 22, 2025
987e061
Merge branch 'main' into feat/retry-restore
Logaka Dec 23, 2025
398e385
feat: implement retry restore
Logaka Dec 25, 2025
ed61408
refactor: conditional return to stream map
Logaka Dec 25, 2025
a20d0e6
Merge branch 'feat/restore-dryRun' into feat/retry-restore
Logaka Dec 25, 2025
ea34805
refactor: prevent lock method duplication
Logaka Dec 26, 2025
e912a3f
refactor: add validation for RestoreRequest
Logaka Jan 16, 2026
8ad5d86
refactor: make databaseKind join conditional
Logaka Jan 19, 2026
9f6d293
refactor: make kinds validation flexible
Logaka Jan 19, 2026
371fb8a
feat: add new attribute to Classifier entity
Logaka Jan 20, 2026
f405a8e
refactor: change restore processing exceptions
Logaka Jan 20, 2026
8034dbe
refactor: rewrite collision catching logic
Logaka Jan 20, 2026
2ec4e9d
Merge branch 'feat/backupV2-add-complex-filtering' into feat/restore-…
Logaka Jan 21, 2026
a0bf7cd
Merge branch 'feat/retry-restore' into feat/restore-dryRun
Logaka Jan 21, 2026
8856251
chore: reformat code
Logaka Jan 21, 2026
1d55eac
feat: add validation annotations for dto
Logaka Jan 22, 2026
76cdd39
chore: rename method, logs, error messages
Logaka Jan 22, 2026
b108e69
refactor: simplify mapping by removing BackupExternalDelegate
Logaka Jan 22, 2026
5d63dca
feat: prevent restore empty databases
Logaka Jan 26, 2026
2194470
Merge branch 'main' into feat/restore-dryRun
Logaka Jan 26, 2026
5862147
fix: adding missed statuses for OpenApi
Logaka Jan 28, 2026
b85d622
refactor: separate mapping, enriching classifiers operation
Logaka Jan 28, 2026
b3fbe74
fix: remove NOT_STARTED status from aggregate method and fix mapping
Logaka Jan 29, 2026
b56a238
style: remove redundant required attribute
Logaka Jan 29, 2026
98874ae
style: fix OpenApi spec
Logaka Jan 30, 2026
1260506
feat: add new flyway migration that mark all status fields in entitie…
Logaka Jan 30, 2026
09d947b
fix: add default status to logical backup
Logaka Jan 30, 2026
d4a010f
fix: take out try catch block from transactional method
Logaka Jan 30, 2026
76838e3
chore: remove unused imports
Logaka Jan 30, 2026
ed2b81e
refactor: invert methods
Logaka Jan 30, 2026
ec959f2
refactor: replace entity builder with constructor
Logaka Jan 30, 2026
d313d81
refactor: replace entity builder with constructor
Logaka Feb 2, 2026
8d49e74
feat: replace auto generating id to manually
Logaka Feb 2, 2026
4a4b0ab
feat: rewrite retryRestore
Logaka Feb 2, 2026
a1d76c3
Merge branch 'main' into feat/restore-dryRun
ArkuNC Feb 3, 2026
157b3a0
style: detailed OpenApi
Logaka Feb 5, 2026
0b72355
chore: rename entities and dtos
Logaka Feb 5, 2026
d14faa0
style: update OpenApi
Logaka Feb 5, 2026
b4d300b
chore: update entity
Logaka Feb 5, 2026
948be71
refactor: simplify first classifier selection
Logaka Feb 5, 2026
2f45197
feat: add new api to internal usage
Logaka Feb 6, 2026
6ce1059
Merge branch 'main' into feat/restore-dryRun
Logaka Feb 6, 2026
eacb0a2
chore: remove unused injection
Logaka Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<OperationAlreadyRunningException> {

@Context
UriInfo uriInfo;

@Override
public Response toResponse(OperationAlreadyRunningException e) {
return buildDefaultResponse(uriInfo, e, Response.Status.CONFLICT);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.persistence.AttributeConverter;

import java.io.IOException;
import java.util.SortedMap;

Expand All @@ -24,7 +24,8 @@ public String convertToDatabaseColumn(SortedMap<Object, Object> attribute) {
@Override
public SortedMap<Object, Object> convertToEntityAttribute(String dbData) {
try {
return objectMapper.readValue(dbData, new TypeReference<SortedMap<Object, Object>>() {});
return objectMapper.readValue(dbData, new TypeReference<SortedMap<Object, Object>>() {
});
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.netcracker.cloud.dbaas.dao.jpa;

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;
Expand All @@ -12,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;

Expand Down Expand Up @@ -141,6 +137,11 @@ public List<DatabaseRegistry> findAllTransactionalDatabaseRegistries(String name
return databaseRegistryRepository.findAllByNamespaceAndDatabase_BgVersionNull(namespace);
}

@Override
public List<DatabaseRegistry> findAllDatabasesByFilter(List<Filter> filters) {
return databaseRegistryRepository.findAllDatabasesByFilter(filters);
}

@Override
public void delete(DatabaseRegistry databaseRegistry) {
log.debug("Delete logical database with classifier {} and type {}", databaseRegistry.getClassifier(), databaseRegistry.getType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 backup database",
examples = {"550e8400-e29b-41d4-a716-446655440000"},
required = true
)
private UUID id;
@Schema(
description = "Name of the database",
examples = {
Expand All @@ -31,12 +38,12 @@ public class BackupDatabaseResponse {
private List<SortedMap<String, Object>> classifiers;
@Schema(
description = "Database settings as a key-value map",
examples = "{\"key\":value, \"key\":value}"
examples = "{\"key\": \"value\", \"key\": \"value\"}"
)
private Map<String, Object> settings;
@Schema(
description = "List of database users",
examples = "[{\"name\":\"username\",\"role\":\"admin\"}"
examples = "[{\"name\":\"username\",\"role\":\"admin\"}]"
)
private List<User> users;
@Schema(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 backup 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(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
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;
import jakarta.validation.groups.ConvertGroup;
import jakarta.validation.groups.Default;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
Expand All @@ -17,7 +16,7 @@
public class BackupRequest {
@NotBlank
@Schema(
description = "Unique identifier of the backup",
description = "Unique name of the backup",
examples = {
"before-prod-update-20251013T1345-G5s8"
},
Expand Down Expand Up @@ -45,7 +44,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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
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;
import jakarta.validation.groups.ConvertGroup;
import jakarta.validation.groups.Default;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
Expand All @@ -21,32 +20,32 @@
@Schema(description = "Response containing backup operation details")
public class BackupResponse {

@NotBlank
@Schema(
description = "Unique identifier of the backup",
description = "Unique name of the backup",
examples = {
"before-prod-update-20251013T1345-G5s8"
},
required = true
)
private String backupName;
@NotBlank
private String backupName;
@Schema(
description = "Name of the storage backend containing the backup",
examples = {
"s3-backend"
},
required = true
)
private String storageName;
@NotBlank
private String storageName;
@Schema(
description = "Path to the backup file in the storage",
examples = {
"/backups"
},
required = true
)
@NotBlank
private String blobPath;
@Schema(
description = "How to handle external databases during backup",
Expand All @@ -57,20 +56,20 @@ public class BackupResponse {
)
@NotNull
private ExternalDatabaseStrategy externalDatabaseStrategy;
@NotNull
@Schema(
description = "Whether external databases were skipped during the backup",
description = "Whether non‑backupable databases were ignored during 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(
Expand All @@ -84,20 +83,23 @@ public class BackupResponse {
"5"
}
)
@NotNull
private Integer total;
@Schema(
description = "Number of databases successfully backed up",
examples = {
"3"
}
)
@NotNull
private Integer completed;
@Schema(
description = "Total size of the backup in bytes",
examples = {
"1073741824"
}
)
@NotNull
private Long size;
@Schema(
description = "Error details if the backup failed",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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 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\"}",
required = true
)
private SortedMap<String, Object> classifier;

@Schema(
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<String, Object> classifierBeforeMapper;
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
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;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

import java.util.ArrayList;
import java.util.List;

@Data
@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. `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.")
"1. `include`\n" +
"2. `exclude`")
public class FilterCriteria {
@Schema(
description = "Apply the filter to the remaining databases",
required = true
)
@NotNull(groups = {BackupGroup.class})
@Size(min = 1, groups = {BackupGroup.class})
private List<Filter> filter;
@Schema(
description = "Include databases that match any of the filters in the list"
)
private List<Filter> include;
@NotNull(groups = {BackupGroup.class})
@Size(min = 1, groups = {BackupGroup.class}, message = "there should be at least one filter specified")
@Valid
private List<Filter> include = new ArrayList<>();

@Schema(
description = "Exclude databases that match any of the filters in the list"
)
private List<Filter> exclude;
@Valid
private List<Filter> exclude = new ArrayList<>();
}
Loading
Loading