From 0f80d4627cf76651be8cccf9a0258d19271bde67 Mon Sep 17 00:00:00 2001
From: Charles Wang <wang.charles234@gmail.com>
Date: Fri, 10 Jan 2025 15:45:06 -0500
Subject: [PATCH] [PLAT-16152] Correctly delete PITR configs on A -> B and A ->
 C xcluster config setups with same db

Summary:
Allows PITR config to be correctly deleted for DR/xcluster configurations with A -> B and A -> C setups using the same databases. The databases on A will use the same PITR configs for both replications. Hence, make sure that if A -> B or A -> C is deleted, the pitr configs on A are retained and not deleted.

Note that there are still issues with A -> B and A -> C if the pitr params are different which cannot be solved with the current implementation.

Test Plan:
UTs

Manually tested following flow:
Create A -> B and A -> C with the same database and pitr params. Check that there is one pitr config for the database on A. Delete A -> C replication. Validate that the pitr conifg

Reviewers: hzare, jmak

Reviewed By: hzare

Subscribers: yugaware

Differential Revision: https://phorge.dev.yugabyte.com/D41172
---
 .../commissioner/tasks/UniverseTaskBase.java  |   3 +-
 .../com/yugabyte/yw/models/PitrConfig.java    |  11 ++
 .../yugabyte/yw/models/PitrConfigTest.java    | 103 ++++++++++++++++++
 3 files changed, 116 insertions(+), 1 deletion(-)
 create mode 100644 managed/src/test/java/com/yugabyte/yw/models/PitrConfigTest.java

diff --git a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UniverseTaskBase.java b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UniverseTaskBase.java
index f7a7169bb7a0..ac6fbd49cde1 100644
--- a/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UniverseTaskBase.java
+++ b/managed/src/main/java/com/yugabyte/yw/commissioner/tasks/UniverseTaskBase.java
@@ -5951,7 +5951,8 @@ protected void createDeleteXClusterConfigSubtasks(
                               && pitrConfig
                                   .getUniverse()
                                   .getUniverseUUID()
-                                  .equals(xClusterConfig.getSourceUniverseUUID()))
+                                  .equals(xClusterConfig.getSourceUniverseUUID())
+                              && pitrConfig.getXClusterConfigs().size() <= 1)
                           || (deleteTargetPitrConfigs
                               && pitrConfig
                                   .getUniverse()
diff --git a/managed/src/main/java/com/yugabyte/yw/models/PitrConfig.java b/managed/src/main/java/com/yugabyte/yw/models/PitrConfig.java
index 04e8b2c5920e..930ff1ee99f2 100644
--- a/managed/src/main/java/com/yugabyte/yw/models/PitrConfig.java
+++ b/managed/src/main/java/com/yugabyte/yw/models/PitrConfig.java
@@ -6,6 +6,7 @@
 
 import com.fasterxml.jackson.annotation.JsonBackReference;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.yugabyte.yw.common.PlatformServiceException;
 import com.yugabyte.yw.forms.CreatePitrConfigParams;
@@ -27,6 +28,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.stream.Collectors;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.extern.slf4j.Slf4j;
@@ -161,4 +163,13 @@ public boolean isUsedForXCluster() {
     List<SqlRow> sqlRow = DB.sqlQuery(sqlStatement).setParameter("pitrUuid", this.uuid).findList();
     return !sqlRow.isEmpty();
   }
+
+  @JsonIgnore
+  public List<XClusterConfig> getXClusterConfigs() {
+    String sqlStatement = "SELECT xcluster_uuid FROM xcluster_pitr WHERE pitr_uuid = :pitrUuid";
+    List<SqlRow> sqlRow = DB.sqlQuery(sqlStatement).setParameter("pitrUuid", this.uuid).findList();
+    return sqlRow.stream()
+        .map(row -> XClusterConfig.getOrBadRequest(UUID.fromString(row.getString("xcluster_uuid"))))
+        .collect(Collectors.toList());
+  }
 }
diff --git a/managed/src/test/java/com/yugabyte/yw/models/PitrConfigTest.java b/managed/src/test/java/com/yugabyte/yw/models/PitrConfigTest.java
new file mode 100644
index 000000000000..4eefd6bf3f7b
--- /dev/null
+++ b/managed/src/test/java/com/yugabyte/yw/models/PitrConfigTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) YugaByte, Inc.
+package com.yugabyte.yw.models;
+
+import static com.yugabyte.yw.common.ModelFactory.createUniverse;
+import static org.junit.Assert.assertEquals;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.yugabyte.yw.common.FakeDBApplication;
+import com.yugabyte.yw.common.ModelFactory;
+import com.yugabyte.yw.forms.CreatePitrConfigParams;
+import com.yugabyte.yw.forms.DrConfigCreateForm.PitrParams;
+import com.yugabyte.yw.forms.XClusterConfigCreateFormData.BootstrapParams.BootstrapBackupParams;
+import com.yugabyte.yw.models.configs.CustomerConfig;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.Before;
+import org.junit.Test;
+import org.yb.CommonTypes.TableType;
+import play.libs.Json;
+
+public class PitrConfigTest extends FakeDBApplication {
+
+  private CustomerConfig config;
+
+  @Before
+  public void setUp() {
+    Customer defaultCustomer = ModelFactory.testCustomer();
+    config = createData(defaultCustomer);
+  }
+
+  private CustomerConfig createData(Customer customer) {
+    JsonNode formData =
+        Json.parse(
+            "{\"name\": \"Test\", \"configName\": \"Test\", \"type\": "
+                + "\"STORAGE\", \"data\": {\"foo\": \"bar\"},"
+                + "\"configUUID\": \"5e8e4887-343b-47dd-a126-71c822904c06\"}");
+    return CustomerConfig.createWithFormData(customer.getUuid(), formData);
+  }
+
+  @Test
+  public void testPitrConfigXClusterCount() {
+
+    Universe sourceUniverse = createUniverse("source Universe");
+    Universe targetUniverse = createUniverse("target Universe");
+
+    BootstrapBackupParams backupRequestParams;
+    backupRequestParams = new BootstrapBackupParams();
+    backupRequestParams.storageConfigUUID = config.getConfigUUID();
+
+    Set<String> sourceDbIds = Set.of("db1", "db2");
+    DrConfig drConfig1 =
+        DrConfig.create(
+            "replication1",
+            sourceUniverse.getUniverseUUID(),
+            targetUniverse.getUniverseUUID(),
+            backupRequestParams,
+            new PitrParams(),
+            sourceDbIds);
+    XClusterConfig xClusterConfig1 = drConfig1.getActiveXClusterConfig();
+
+    CreatePitrConfigParams createPitrConfigParams = new CreatePitrConfigParams();
+    createPitrConfigParams.setUniverseUUID(sourceUniverse.getUniverseUUID());
+    createPitrConfigParams.customerUUID = Customer.get(sourceUniverse.getCustomerId()).getUuid();
+    createPitrConfigParams.name = null;
+    createPitrConfigParams.keyspaceName = "mockNamespace";
+    createPitrConfigParams.tableType = TableType.PGSQL_TABLE_TYPE;
+    createPitrConfigParams.retentionPeriodInSeconds = 1;
+    createPitrConfigParams.xClusterConfig = xClusterConfig1;
+    createPitrConfigParams.intervalInSeconds = 1;
+    createPitrConfigParams.createdForDr = true;
+
+    PitrConfig pitrConfig = PitrConfig.create(UUID.randomUUID(), createPitrConfigParams);
+    xClusterConfig1.addPitrConfig(pitrConfig);
+    pitrConfig.refresh();
+
+    assertEquals(1, pitrConfig.getXClusterConfigs().size());
+    assertEquals(xClusterConfig1.getUuid(), pitrConfig.getXClusterConfigs().get(0).getUuid());
+
+    Universe secondTargetUniverse = createUniverse("second target Universe");
+    // A -> B and A -> C.
+    DrConfig drConfig2 =
+        DrConfig.create(
+            "replication2",
+            sourceUniverse.getUniverseUUID(),
+            secondTargetUniverse.getUniverseUUID(),
+            backupRequestParams,
+            new PitrParams(),
+            sourceDbIds);
+    XClusterConfig xClusterConfig2 = drConfig2.getActiveXClusterConfig();
+    xClusterConfig2.addPitrConfig(pitrConfig);
+
+    // Pitr Config is associated to 2 xcluster configs.
+    pitrConfig.refresh();
+    assertEquals(2, pitrConfig.getXClusterConfigs().size());
+    assertEquals(xClusterConfig2.getUuid(), pitrConfig.getXClusterConfigs().get(1).getUuid());
+
+    // xcluster_pitr should be cascade deleted when xcluster config is deleted.
+    xClusterConfig2.delete();
+    pitrConfig.refresh();
+    assertEquals(1, pitrConfig.getXClusterConfigs().size());
+    assertEquals(xClusterConfig1.getUuid(), pitrConfig.getXClusterConfigs().get(0).getUuid());
+  }
+}