From a71c80169f8cb748444dd3a595e208dc37e40338 Mon Sep 17 00:00:00 2001 From: Yury Shchetinin Date: Mon, 18 Dec 2023 16:51:16 +0000 Subject: [PATCH] [BACKPORT 2.18.5][PLAT-10616] Support SpecificGflags in audit Summary: Original commit: b82605241492754ad61519701c727568d746d92a/D29248 Added support for SpecificGflags in gflags audit. Also covered the case, when read replica has overriden gflags. In that case audit will contain "readonly_cluster_gflags" field, for example: ``` { "gflags": { "master": [ { "name": "master", "old": "1", "new": "2", "default": "master" } ], "tserver": [ { "name": "tserver", "old": "1", "new": "3", "default": "tserver" } ] }, "readonly_cluster_gflags": { "master": [ { "name": "master", "old": "5", "new": "1", "default": "master" } ], "tserver": [ { "name": "tserver2", "old": "2", "new": null, "default": "tserver2" }, { "name": "tserver", "old": null, "new": "2", "default": "tserver" } ] } } ``` Test Plan: sbt test Manual testing: Run gflags upgrade on universe and verify audit data Reviewers: #yba-api-review, cwang, anijhawan, yshchetinin Reviewed By: #yba-api-review, cwang, anijhawan Subscribers: dshubin, yugaware Tags: #jenkins-ready Differential Revision: https://phorge.dev.yugabyte.com/D31063 --- .../yugabyte/yw/common/gflags/GFlagsUtil.java | 11 ++ .../UpgradeUniverseController.java | 16 +- .../handlers/GFlagsAuditHandler.java | 135 +++++++++++++ .../handlers/UpgradeUniverseHandler.java | 79 -------- .../forms/UniverseDefinitionTaskParams.java | 4 +- .../handlers/UpgradeUniverseHandlerTest.java | 180 +++++++++++++++++- 6 files changed, 327 insertions(+), 98 deletions(-) create mode 100644 managed/src/main/java/com/yugabyte/yw/controllers/handlers/GFlagsAuditHandler.java diff --git a/managed/src/main/java/com/yugabyte/yw/common/gflags/GFlagsUtil.java b/managed/src/main/java/com/yugabyte/yw/common/gflags/GFlagsUtil.java index e24de09003fb..34b010bb029b 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/gflags/GFlagsUtil.java +++ b/managed/src/main/java/com/yugabyte/yw/common/gflags/GFlagsUtil.java @@ -1135,4 +1135,15 @@ public static Set getDeletedGFlags( .filter(flag -> !updatedGFlags.containsKey(flag)) .collect(Collectors.toSet()); } + + public static boolean areGflagsInheritedFromPrimary( + UniverseDefinitionTaskParams.Cluster cluster) { + if (cluster.clusterType != UniverseDefinitionTaskParams.ClusterType.ASYNC) { + return false; + } + if (cluster.userIntent.specificGFlags == null) { + return true; + } + return cluster.userIntent.specificGFlags.isInheritFromPrimary(); + } } diff --git a/managed/src/main/java/com/yugabyte/yw/controllers/UpgradeUniverseController.java b/managed/src/main/java/com/yugabyte/yw/controllers/UpgradeUniverseController.java index c314fa5fa8bf..bfbfea11999e 100644 --- a/managed/src/main/java/com/yugabyte/yw/controllers/UpgradeUniverseController.java +++ b/managed/src/main/java/com/yugabyte/yw/controllers/UpgradeUniverseController.java @@ -8,6 +8,7 @@ import com.yugabyte.yw.common.config.RuntimeConfGetter; import com.yugabyte.yw.common.config.RuntimeConfigFactory; import com.yugabyte.yw.common.config.UniverseConfKeys; +import com.yugabyte.yw.controllers.handlers.GFlagsAuditHandler; import com.yugabyte.yw.controllers.handlers.UpgradeUniverseHandler; import com.yugabyte.yw.forms.CertsRotateParams; import com.yugabyte.yw.forms.GFlagsUpgradeParams; @@ -19,7 +20,6 @@ import com.yugabyte.yw.forms.SystemdUpgradeParams; import com.yugabyte.yw.forms.ThirdpartySoftwareUpgradeParams; import com.yugabyte.yw.forms.TlsToggleParams; -import com.yugabyte.yw.forms.UniverseDefinitionTaskParams.UserIntent; import com.yugabyte.yw.forms.UpgradeTaskParams; import com.yugabyte.yw.forms.VMImageUpgradeParams; import com.yugabyte.yw.models.Audit; @@ -32,7 +32,6 @@ import io.swagger.annotations.Authorization; import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import play.libs.Json; import play.mvc.Http; import play.mvc.Http.Request; import play.mvc.Result; @@ -49,6 +48,8 @@ public class UpgradeUniverseController extends AuthenticatedController { @Inject RuntimeConfGetter confGetter; + @Inject GFlagsAuditHandler gFlagsAuditHandler; + /** * API that restarts all nodes in the universe. Supports rolling and non-rolling restart * @@ -403,19 +404,12 @@ private Result requestHandler( universe.getName(), universe.getUniverseUUID(), customer.getUuid()); - - // prevent race condition in the case userIntent updates before we createAuditEntry - UserIntent userIntent = - Json.fromJson( - Json.toJson(universe.getUniverseDetails().getPrimaryCluster().userIntent), - UserIntent.class); - UUID taskUuid = serviceMethod.upgrade(requestParams, customer, universe); JsonNode additionalDetails = null; if (type.equals(GFlagsUpgradeParams.class)) { additionalDetails = - upgradeUniverseHandler.constructGFlagAuditPayload( - (GFlagsUpgradeParams) requestParams, userIntent); + gFlagsAuditHandler.constructGFlagAuditPayload((GFlagsUpgradeParams) requestParams); } + UUID taskUuid = serviceMethod.upgrade(requestParams, customer, universe); auditService() .createAuditEntryWithReqBody( request, diff --git a/managed/src/main/java/com/yugabyte/yw/controllers/handlers/GFlagsAuditHandler.java b/managed/src/main/java/com/yugabyte/yw/controllers/handlers/GFlagsAuditHandler.java new file mode 100644 index 000000000000..0e7eaf71bbef --- /dev/null +++ b/managed/src/main/java/com/yugabyte/yw/controllers/handlers/GFlagsAuditHandler.java @@ -0,0 +1,135 @@ +// Copyright (c) YugaByte, Inc. + +package com.yugabyte.yw.controllers.handlers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; +import com.yugabyte.yw.commissioner.tasks.UniverseTaskBase; +import com.yugabyte.yw.common.PlatformServiceException; +import com.yugabyte.yw.common.gflags.GFlagDetails; +import com.yugabyte.yw.common.gflags.GFlagDiffEntry; +import com.yugabyte.yw.common.gflags.GFlagsAuditPayload; +import com.yugabyte.yw.common.gflags.GFlagsUtil; +import com.yugabyte.yw.forms.GFlagsUpgradeParams; +import com.yugabyte.yw.forms.UniverseDefinitionTaskParams; +import com.yugabyte.yw.models.Universe; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; + +@Singleton +@Slf4j +public class GFlagsAuditHandler { + + private final GFlagsValidationHandler gFlagsValidationHandler; + + @Inject + public GFlagsAuditHandler(GFlagsValidationHandler gFlagsValidationHandler) { + this.gFlagsValidationHandler = gFlagsValidationHandler; + } + + public JsonNode constructGFlagAuditPayload(GFlagsUpgradeParams requestParams) { + Universe universe = Universe.getOrBadRequest(requestParams.getUniverseUUID()); + Map newVersionsOfClusters = + requestParams.getNewVersionsOfClusters(universe); + Map oldVersionsOfClusters = + universe.getUniverseDetails().clusters.stream() + .collect(Collectors.toMap(c -> c.uuid, c -> c)); + String softwareVersion = requestParams.getPrimaryCluster().userIntent.ybSoftwareVersion; + + Map auditPayload = new HashMap<>(); + + for (UniverseDefinitionTaskParams.Cluster newCluster : newVersionsOfClusters.values()) { + Map newMasterGFlags = + GFlagsUtil.getBaseGFlags( + UniverseTaskBase.ServerType.MASTER, newCluster, newVersionsOfClusters.values()); + Map newTserverGFlags = + GFlagsUtil.getBaseGFlags( + UniverseTaskBase.ServerType.TSERVER, newCluster, newVersionsOfClusters.values()); + UniverseDefinitionTaskParams.Cluster oldCluster = oldVersionsOfClusters.get(newCluster.uuid); + Map oldMasterGFlags = + GFlagsUtil.getBaseGFlags( + UniverseTaskBase.ServerType.MASTER, oldCluster, oldVersionsOfClusters.values()); + Map oldTserverGFlags = + GFlagsUtil.getBaseGFlags( + UniverseTaskBase.ServerType.TSERVER, oldCluster, oldVersionsOfClusters.values()); + if (!Objects.equals(newMasterGFlags, oldMasterGFlags) + || !Objects.equals(newTserverGFlags, oldTserverGFlags)) { + GFlagsAuditPayload payload = new GFlagsAuditPayload(); + + payload.master = + generateGFlagEntries( + oldMasterGFlags, + newMasterGFlags, + UniverseTaskBase.ServerType.MASTER.toString(), + softwareVersion); + payload.tserver = + generateGFlagEntries( + oldTserverGFlags, + newTserverGFlags, + UniverseTaskBase.ServerType.TSERVER.toString(), + softwareVersion); + + if (newCluster.clusterType == UniverseDefinitionTaskParams.ClusterType.PRIMARY) { + auditPayload.put("gflags", payload); + } else if (!GFlagsUtil.areGflagsInheritedFromPrimary(newCluster) + || !GFlagsUtil.areGflagsInheritedFromPrimary(oldCluster)) { + auditPayload.put("readonly_cluster_gflags", payload); + } + } + } + ObjectMapper mapper = new ObjectMapper(); + return mapper.valueToTree(auditPayload); + } + + public List generateGFlagEntries( + Map oldGFlags, + Map newGFlags, + String serverType, + String softwareVersion) { + List gFlagChanges = new ArrayList<>(); + if (oldGFlags == null) { + oldGFlags = new HashMap<>(); + } + if (newGFlags == null) { + newGFlags = new HashMap<>(); + } + GFlagDiffEntry tEntry; + Collection modifiedGFlags = Sets.union(oldGFlags.keySet(), newGFlags.keySet()); + + for (String gFlagName : modifiedGFlags) { + String oldGFlagValue = oldGFlags.getOrDefault(gFlagName, null); + String newGFlagValue = newGFlags.getOrDefault(gFlagName, null); + if (oldGFlagValue == null || !oldGFlagValue.equals(newGFlagValue)) { + String defaultGFlagValue = getGFlagDefaultValue(softwareVersion, serverType, gFlagName); + tEntry = new GFlagDiffEntry(gFlagName, oldGFlagValue, newGFlagValue, defaultGFlagValue); + gFlagChanges.add(tEntry); + } + } + + return gFlagChanges; + } + + private String getGFlagDefaultValue(String softwareVersion, String serverType, String gFlagName) { + GFlagDetails defaultGFlag; + String defaultGFlagValue; + try { + defaultGFlag = + gFlagsValidationHandler.getGFlagsMetadata(softwareVersion, serverType, gFlagName); + } catch (IOException | PlatformServiceException e) { + defaultGFlag = null; + } + defaultGFlagValue = (defaultGFlag == null) ? null : defaultGFlag.defaultValue; + return defaultGFlagValue; + } +} diff --git a/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandler.java b/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandler.java index eef7c9450aa7..c13f9184dcc2 100644 --- a/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandler.java +++ b/managed/src/main/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandler.java @@ -2,14 +2,10 @@ package com.yugabyte.yw.controllers.handlers; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Sets; import com.google.inject.Inject; import com.yugabyte.yw.commissioner.Commissioner; import com.yugabyte.yw.commissioner.Common.CloudType; -import com.yugabyte.yw.commissioner.tasks.UniverseTaskBase.ServerType; import com.yugabyte.yw.common.KubernetesManagerFactory; import com.yugabyte.yw.common.PlatformServiceException; import com.yugabyte.yw.common.Util; @@ -19,9 +15,6 @@ import com.yugabyte.yw.common.config.ProviderConfKeys; import com.yugabyte.yw.common.config.RuntimeConfGetter; import com.yugabyte.yw.common.config.RuntimeConfigFactory; -import com.yugabyte.yw.common.gflags.GFlagDetails; -import com.yugabyte.yw.common.gflags.GFlagDiffEntry; -import com.yugabyte.yw.common.gflags.GFlagsAuditPayload; import com.yugabyte.yw.common.gflags.GFlagsUtil; import com.yugabyte.yw.forms.CertsRotateParams; import com.yugabyte.yw.forms.GFlagsUpgradeParams; @@ -42,12 +35,6 @@ import com.yugabyte.yw.models.Provider; import com.yugabyte.yw.models.Universe; import com.yugabyte.yw.models.helpers.TaskType; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.UUID; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; @@ -60,7 +47,6 @@ public class UpgradeUniverseHandler { private final Commissioner commissioner; private final KubernetesManagerFactory kubernetesManagerFactory; private final RuntimeConfigFactory runtimeConfigFactory; - private final GFlagsValidationHandler gFlagsValidationHandler; private final YbcManager ybcManager; private final RuntimeConfGetter confGetter; private final CertificateHelper certificateHelper; @@ -70,14 +56,12 @@ public UpgradeUniverseHandler( Commissioner commissioner, KubernetesManagerFactory kubernetesManagerFactory, RuntimeConfigFactory runtimeConfigFactory, - GFlagsValidationHandler gFlagsValidationHandler, YbcManager ybcManager, RuntimeConfGetter confGetter, CertificateHelper certificateHelper) { this.commissioner = commissioner; this.kubernetesManagerFactory = kubernetesManagerFactory; this.runtimeConfigFactory = runtimeConfigFactory; - this.gFlagsValidationHandler = gFlagsValidationHandler; this.ybcManager = ybcManager; this.confGetter = confGetter; this.certificateHelper = certificateHelper; @@ -214,69 +198,6 @@ public UUID upgradeKubernetesOverrides( universe); } - public JsonNode constructGFlagAuditPayload( - GFlagsUpgradeParams requestParams, UserIntent oldUserIntent) { - if (requestParams.getPrimaryCluster() == null) { - return null; - } - // TODO: support specific gflags - UserIntent newUserIntent = requestParams.getPrimaryCluster().userIntent; - Map newMasterGFlags = newUserIntent.masterGFlags; - Map newTserverGFlags = newUserIntent.tserverGFlags; - Map oldMasterGFlags = oldUserIntent.masterGFlags; - Map oldTserverGFlags = oldUserIntent.tserverGFlags; - GFlagsAuditPayload payload = new GFlagsAuditPayload(); - String softwareVersion = newUserIntent.ybSoftwareVersion; - payload.master = - generateGFlagEntries( - oldMasterGFlags, newMasterGFlags, ServerType.MASTER.toString(), softwareVersion); - payload.tserver = - generateGFlagEntries( - oldTserverGFlags, newTserverGFlags, ServerType.TSERVER.toString(), softwareVersion); - - ObjectMapper mapper = new ObjectMapper(); - Map auditPayload = new HashMap<>(); - auditPayload.put("gflags", payload); - - return mapper.valueToTree(auditPayload); - } - - public List generateGFlagEntries( - Map oldGFlags, - Map newGFlags, - String serverType, - String softwareVersion) { - List gFlagChanges = new ArrayList(); - - GFlagDiffEntry tEntry; - Collection modifiedGFlags = Sets.union(oldGFlags.keySet(), newGFlags.keySet()); - - for (String gFlagName : modifiedGFlags) { - String oldGFlagValue = oldGFlags.getOrDefault(gFlagName, null); - String newGFlagValue = newGFlags.getOrDefault(gFlagName, null); - if (oldGFlagValue == null || !oldGFlagValue.equals(newGFlagValue)) { - String defaultGFlagValue = getGFlagDefaultValue(softwareVersion, serverType, gFlagName); - tEntry = new GFlagDiffEntry(gFlagName, oldGFlagValue, newGFlagValue, defaultGFlagValue); - gFlagChanges.add(tEntry); - } - } - - return gFlagChanges; - } - - public String getGFlagDefaultValue(String softwareVersion, String serverType, String gFlagName) { - GFlagDetails defaultGFlag; - String defaultGFlagValue; - try { - defaultGFlag = - gFlagsValidationHandler.getGFlagsMetadata(softwareVersion, serverType, gFlagName); - } catch (IOException | PlatformServiceException e) { - defaultGFlag = null; - } - defaultGFlagValue = (defaultGFlag == null) ? null : defaultGFlag.defaultValue; - return defaultGFlagValue; - } - public UUID rotateCerts(CertsRotateParams requestParams, Customer customer, Universe universe) { log.debug( "rotateCerts called with rootCA: {}", diff --git a/managed/src/main/java/com/yugabyte/yw/forms/UniverseDefinitionTaskParams.java b/managed/src/main/java/com/yugabyte/yw/forms/UniverseDefinitionTaskParams.java index 74ba725b6839..76e987f28c1b 100644 --- a/managed/src/main/java/com/yugabyte/yw/forms/UniverseDefinitionTaskParams.java +++ b/managed/src/main/java/com/yugabyte/yw/forms/UniverseDefinitionTaskParams.java @@ -594,7 +594,9 @@ public UserIntent clone() { newUserIntent.provider = provider; newUserIntent.providerType = providerType; newUserIntent.replicationFactor = replicationFactor; - newUserIntent.regionList = new ArrayList<>(regionList); + if (regionList != null) { + newUserIntent.regionList = new ArrayList<>(regionList); + } newUserIntent.preferredRegion = preferredRegion; newUserIntent.instanceType = instanceType; newUserIntent.numNodes = numNodes; diff --git a/managed/src/test/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandlerTest.java b/managed/src/test/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandlerTest.java index 57151553315f..8cd672752d54 100644 --- a/managed/src/test/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandlerTest.java +++ b/managed/src/test/java/com/yugabyte/yw/controllers/handlers/UpgradeUniverseHandlerTest.java @@ -3,22 +3,37 @@ package com.yugabyte.yw.controllers.handlers; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; import com.yugabyte.yw.commissioner.Commissioner; import com.yugabyte.yw.commissioner.tasks.UniverseTaskBase.ServerType; +import com.yugabyte.yw.common.ApiUtils; import com.yugabyte.yw.common.FakeDBApplication; import com.yugabyte.yw.common.KubernetesManagerFactory; +import com.yugabyte.yw.common.ModelFactory; import com.yugabyte.yw.common.backuprestore.ybc.YbcManager; import com.yugabyte.yw.common.certmgmt.CertificateHelper; import com.yugabyte.yw.common.config.RuntimeConfGetter; import com.yugabyte.yw.common.config.RuntimeConfigFactory; +import com.yugabyte.yw.common.gflags.GFlagDetails; import com.yugabyte.yw.common.gflags.GFlagDiffEntry; +import com.yugabyte.yw.common.gflags.SpecificGFlags; +import com.yugabyte.yw.forms.GFlagsUpgradeParams; import com.yugabyte.yw.forms.ITaskParams; import com.yugabyte.yw.forms.TlsToggleParams; import com.yugabyte.yw.forms.UniverseDefinitionTaskParams; +import com.yugabyte.yw.models.Customer; +import com.yugabyte.yw.models.Universe; import com.yugabyte.yw.models.helpers.TaskType; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,24 +44,26 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import play.libs.Json; @RunWith(JUnitParamsRunner.class) public class UpgradeUniverseHandlerTest extends FakeDBApplication { - private static final String DEFAULT_INSTANCE_TYPE = "type1"; - private static final String NEW_INSTANCE_TYPE = "type2"; private UpgradeUniverseHandler handler; + private GFlagsAuditHandler gFlagsAuditHandler; + private GFlagsValidationHandler gFlagsValidationHandler; @Before public void setUp() { Commissioner mockCommissioner = mock(Commissioner.class); when(mockCommissioner.submit(any(TaskType.class), any(ITaskParams.class))) .thenReturn(UUID.randomUUID()); + gFlagsValidationHandler = mock(GFlagsValidationHandler.class); + gFlagsAuditHandler = new GFlagsAuditHandler(gFlagsValidationHandler); handler = new UpgradeUniverseHandler( mockCommissioner, mock(KubernetesManagerFactory.class), mock(RuntimeConfigFactory.class), - mock(GFlagsValidationHandler.class), mock(YbcManager.class), mock(RuntimeConfGetter.class), mock(CertificateHelper.class)); @@ -99,7 +116,7 @@ public void testGenerateGFlagEntries() { // removed gflag oldGFlags.put("stderrthreshold", "1"); List gFlagDiffEntries = - handler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); + gFlagsAuditHandler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); Assert.assertEquals(1, gFlagDiffEntries.size()); Assert.assertEquals("stderrthreshold", gFlagDiffEntries.get(0).name); Assert.assertEquals("1", gFlagDiffEntries.get(0).oldValue); @@ -110,7 +127,7 @@ public void testGenerateGFlagEntries() { newGFlags.clear(); newGFlags.put("minloglevel", "2"); gFlagDiffEntries = - handler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); + gFlagsAuditHandler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); Assert.assertEquals("minloglevel", gFlagDiffEntries.get(0).name); Assert.assertEquals(null, gFlagDiffEntries.get(0).oldValue); Assert.assertEquals("2", gFlagDiffEntries.get(0).newValue); @@ -121,7 +138,7 @@ public void testGenerateGFlagEntries() { oldGFlags.put("max_log_size", "0"); newGFlags.put("max_log_size", "1000"); gFlagDiffEntries = - handler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); + gFlagsAuditHandler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); Assert.assertEquals("max_log_size", gFlagDiffEntries.get(0).name); Assert.assertEquals("0", gFlagDiffEntries.get(0).oldValue); Assert.assertEquals("1000", gFlagDiffEntries.get(0).newValue); @@ -132,7 +149,156 @@ public void testGenerateGFlagEntries() { oldGFlags.put("max_log_size", "2000"); newGFlags.put("max_log_size", "2000"); gFlagDiffEntries = - handler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); + gFlagsAuditHandler.generateGFlagEntries(oldGFlags, newGFlags, serverType, softwareVersion); Assert.assertEquals(0, gFlagDiffEntries.size()); } + + @Test + public void testConstructGFlagAuditPayload() throws IOException { + initGflagDefaults(); + Customer c = ModelFactory.testCustomer(); + Universe u = ModelFactory.createUniverse(c.getId()); + GFlagsUpgradeParams params = new GFlagsUpgradeParams(); + params.setUniverseUUID(u.getUniverseUUID()); + params.clusters = u.getUniverseDetails().clusters; + params.getPrimaryCluster().userIntent.specificGFlags = + SpecificGFlags.construct(ImmutableMap.of("master", "1"), ImmutableMap.of("tserver", "2")); + JsonNode payload = gFlagsAuditHandler.constructGFlagAuditPayload(params); + ObjectNode expected = Json.newObject(); + expected.set( + "gflags", + constructExpected( + Collections.singletonList(createDiff("master", "1", null)), + Collections.singletonList(createDiff("tserver", "2", null)))); + Assert.assertEquals(expected, payload); + } + + @Test + public void testConstructGFlagAuditPayloadReadReplica() throws IOException { + initGflagDefaults(); + Customer c = ModelFactory.testCustomer(); + Universe u = ModelFactory.createUniverse(c.getId()); + UniverseDefinitionTaskParams.UserIntent rrUserIntent = + u.getUniverseDetails().getPrimaryCluster().userIntent.clone(); + rrUserIntent.specificGFlags = + SpecificGFlags.construct(ImmutableMap.of("master", "5"), ImmutableMap.of("tserver2", "2")); + u = + Universe.saveDetails( + u.getUniverseUUID(), ApiUtils.mockUniverseUpdaterWithReadReplica(rrUserIntent, null)); + GFlagsUpgradeParams params = new GFlagsUpgradeParams(); + params.setUniverseUUID(u.getUniverseUUID()); + params.clusters = u.getUniverseDetails().clusters; + params.getReadOnlyClusters().get(0).userIntent.specificGFlags = + SpecificGFlags.construct(ImmutableMap.of("master", "1"), ImmutableMap.of("tserver", "2")); + JsonNode payload = gFlagsAuditHandler.constructGFlagAuditPayload(params); + ObjectNode expected = Json.newObject(); + expected.set( + "readonly_cluster_gflags", + constructExpected( + Collections.singletonList(createDiff("master", "1", "5")), + Arrays.asList(createDiff("tserver2", null, "2"), createDiff("tserver", "2", null)))); + Assert.assertEquals(expected, payload); + } + + @Test + public void testConstructGFlagAuditPayloadBothChanged() throws IOException { + initGflagDefaults(); + Customer c = ModelFactory.testCustomer(); + Universe u = ModelFactory.createUniverse(c.getId()); + u = + Universe.saveDetails( + u.getUniverseUUID(), + universe -> { + UniverseDefinitionTaskParams details = universe.getUniverseDetails(); + details.getPrimaryCluster().userIntent.specificGFlags = + SpecificGFlags.construct( + ImmutableMap.of("master", "1"), ImmutableMap.of("tserver", "1")); + universe.setUniverseDetails(details); + }); + UniverseDefinitionTaskParams.UserIntent rrUserIntent = + u.getUniverseDetails().getPrimaryCluster().userIntent.clone(); + rrUserIntent.specificGFlags = + SpecificGFlags.construct(ImmutableMap.of("master", "5"), ImmutableMap.of("tserver2", "2")); + u = + Universe.saveDetails( + u.getUniverseUUID(), ApiUtils.mockUniverseUpdaterWithReadReplica(rrUserIntent, null)); + GFlagsUpgradeParams params = new GFlagsUpgradeParams(); + params.setUniverseUUID(u.getUniverseUUID()); + params.clusters = u.getUniverseDetails().clusters; + params.getPrimaryCluster().userIntent.specificGFlags = + SpecificGFlags.construct(ImmutableMap.of("master", "2"), ImmutableMap.of("tserver", "3")); + params.getReadOnlyClusters().get(0).userIntent.specificGFlags = + SpecificGFlags.construct(ImmutableMap.of("master", "1"), ImmutableMap.of("tserver", "2")); + JsonNode payload = gFlagsAuditHandler.constructGFlagAuditPayload(params); + ObjectNode expected = Json.newObject(); + expected.set( + "gflags", + constructExpected( + Collections.singletonList(createDiff("master", "2", "1")), + Collections.singletonList(createDiff("tserver", "3", "1")))); + expected.set( + "readonly_cluster_gflags", + constructExpected( + Collections.singletonList(createDiff("master", "1", "5")), + Arrays.asList(createDiff("tserver2", null, "2"), createDiff("tserver", "2", null)))); + Assert.assertEquals(expected, payload); + } + + @Test + public void testConstructGFlagAuditPayloadReadReplicaInherited() throws IOException { + initGflagDefaults(); + Customer c = ModelFactory.testCustomer(); + Universe u = ModelFactory.createUniverse(c.getId()); + UniverseDefinitionTaskParams.UserIntent rrUserIntent = + u.getUniverseDetails().getPrimaryCluster().userIntent.clone(); + rrUserIntent.specificGFlags = SpecificGFlags.constructInherited(); + u = + Universe.saveDetails( + u.getUniverseUUID(), ApiUtils.mockUniverseUpdaterWithReadReplica(rrUserIntent, null)); + GFlagsUpgradeParams params = new GFlagsUpgradeParams(); + params.setUniverseUUID(u.getUniverseUUID()); + params.clusters = u.getUniverseDetails().clusters; + params.getPrimaryCluster().userIntent.specificGFlags = + SpecificGFlags.construct(ImmutableMap.of("master", "1"), ImmutableMap.of("tserver", "2")); + JsonNode payload = gFlagsAuditHandler.constructGFlagAuditPayload(params); + ObjectNode expected = Json.newObject(); + // Expecting no read replica gflags as long as they are inherited. + expected.set( + "gflags", + constructExpected( + Collections.singletonList(createDiff("master", "1", null)), + Collections.singletonList(createDiff("tserver", "2", null)))); + Assert.assertEquals(expected, payload); + } + + private void initGflagDefaults() throws IOException { + when(gFlagsValidationHandler.getGFlagsMetadata(anyString(), anyString(), anyString())) + .thenAnswer( + invocation -> { + GFlagDetails result = new GFlagDetails(); + result.defaultValue = invocation.getArgument(2); + result.name = invocation.getArgument(2); + return result; + }); + } + + private ObjectNode constructExpected(List master, List tserver) { + ObjectNode res = Json.newObject(); + ArrayNode mastArray = Json.newArray(); + master.forEach(mastArray::add); + ArrayNode tservArray = Json.newArray(); + tserver.forEach(tservArray::add); + res.set("master", mastArray); + res.set("tserver", tservArray); + return res; + } + + private JsonNode createDiff(String gflag, String value, String oldValue) { + ObjectNode res = Json.newObject(); + res.put("name", gflag); + res.put("old", oldValue); + res.put("new", value); + res.put("default", gflag); + return res; + } }