Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Common changes needed to support dynamic en/disabling of config overrides #294

Merged
merged 8 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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,90 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.amazon.opendistro.elasticsearch.performanceanalyzer.config.overrides;
khushbr marked this conversation as resolved.
Show resolved Hide resolved

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

/**
* POJO for config overrides. The class contains two sets of overrides, one for enabling and one
* for disabling.
*/
public class ConfigOverrides {
private Overrides enable;
private Overrides disable;

public ConfigOverrides() {
this.enable = new Overrides();
this.disable = new Overrides();
}

public Overrides getEnable() {
return enable;
}

public void setEnable(Overrides enable) {
this.enable = enable;
}

public Overrides getDisable() {
return disable;
}

public void setDisable(Overrides disable) {
this.disable = disable;
}

/**
* Class containing the overridable attributes of the system. Currently, overriding the
* enabled/disabled state for RCAs, deciders, and actions are supported. More attributes can
* be added as needed.
*/
public static class Overrides {
private List<String> rcas;
private List<String> deciders;
private List<String> actions;

public Overrides() {
this.rcas = new ArrayList<>();
this.deciders = new ArrayList<>();
this.actions = new ArrayList<>();
khushbr marked this conversation as resolved.
Show resolved Hide resolved
}

public List<String> getRcas() {
return rcas;
}

public void setRcas(List<String> rcas) {
this.rcas = rcas;
}

public List<String> getDeciders() {
return deciders;
}

public void setDeciders(List<String> deciders) {
this.deciders = deciders;
}

public List<String> getActions() {
return actions;
}

public void setActions(List<String> actions) {
this.actions = actions;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.amazon.opendistro.elasticsearch.performanceanalyzer.config.overrides;

import com.amazon.opendistro.elasticsearch.performanceanalyzer.util.JsonConverter;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
* Class that helps with operations concerning {@link ConfigOverrides}s
*/
public class ConfigOverridesHelper {

private static final ObjectMapper MAPPER = new ObjectMapper();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a static final object. Some of the decider actions are node level. I don't think this will work with the RCA-IT framework if we just have a static object mapper here, unless the Overrides object haves nodeId as a parameter, in my opinion.

Copy link
Contributor Author

@ktkrg ktkrg Jul 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only to help deserialize the string from ClusterDetailsEventProcessor into the override object. It's then updated in the RcaConf. This change will come in the next PR. This PR is only introducing the common classes needed by both the reader and the writer.

Edit:
It's used only as a helper, and it didn't make sense to create an instance of the helper object and pass it around to serialize and deserialize the overrides so decided to make it static. It does not mutate any state and works only on the arguments supplied.

The ClusterDetailsEventProcessor calls ConfigOverridesHelper.deserialize(lines[1]) in its processEvent method passing in whatever the writer has written as overrides through the NodeDetailsCollector.


/**
* Serializes a {@link ConfigOverrides} instance to its JSON representation.
*
* @param overrides The {@link ConfigOverrides} instance.
* @return String in JSON format representing the serialized equivalent.
* @throws IOException if conversion runs into an IOException.
*/
public static synchronized String serialize(final ConfigOverrides overrides) throws IOException {
// We can't use a local variable to set the exception generated inside the lambda as the
// local variable is not effectively final(because we'll end up mutating the reference).
// In order to fish the exception out, we need to create a wrapper and set the exception
// there instead for the caller to get the value.
final IOException[] exception = new IOException[1];
final String serializedOverrides = AccessController.doPrivileged((PrivilegedAction<String>) () -> {
try {
return MAPPER.writeValueAsString(overrides);
} catch (IOException e) {
exception[0] = e;
}
return "";
});

if (serializedOverrides.isEmpty() && exception[0] != null) {
throw exception[0];
}

return serializedOverrides;
}

/**
* Deserializes a JSON representation of the config overrides into a {@link ConfigOverrides} instance.
*
* @param overrides The JSON string representing config overrides.
* @return A {@link ConfigOverrides} instance if the JSON is valid.
* @throws IOException if conversion runs into an IOException.
*/
public static synchronized ConfigOverrides deserialize(final String overrides) throws IOException {
// We can't use a local variable to set the exception generated inside the lambda as the
// local variable is not effectively final(because we'll end up mutating the reference).
// In order to fish the exception out, we need to create a wrapper and set the exception
// there instead for the caller to get the value.
final IOException[] exception = new IOException[1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain if this is a one element array, why does it needs to be an array, can't it just be a single reference ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't use a local variable inside the lambda because it needs to be effectively final so we need a one element array(or any wrapper around the exception) to fish the exception out of the lambda into the calling code. I've added this as a javadoc comment as well.

final ConfigOverrides configOverrides = AccessController.doPrivileged((PrivilegedAction<ConfigOverrides>) () -> {
try {
return MAPPER.readValue(overrides, ConfigOverrides.class);
} catch (IOException ioe) {
exception[0] = ioe;
}
return null;
});

if (configOverrides == null && exception[0] != null) {
// re throw the exception that was consumed while deserializing.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want to add a metric here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the metric where we call deserialize() in ClusterDetailsEventProcessor, I'll send the PR after this gets merged as that PR depends on this change.

throw exception[0];
}

return configOverrides;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.amazon.opendistro.elasticsearch.performanceanalyzer.config.overrides;

/**
* Class responsible for holding the latest config overrides across the cluster.
*/
public class ConfigOverridesWrapper {

private volatile ConfigOverrides currentClusterConfigOverrides;
private volatile long lastUpdatedTimestamp;

public ConfigOverrides getCurrentClusterConfigOverrides() {
return currentClusterConfigOverrides;
}

/**
* Sets a new ConfigOverrides instance as the current cluster config overrides instance.
*
* @param configOverrides the ConfigOverrides instance.
*/
public void setCurrentClusterConfigOverrides(final ConfigOverrides configOverrides) {
this.currentClusterConfigOverrides = configOverrides;
}

public long getLastUpdatedTimestamp() {
return lastUpdatedTimestamp;
}

public void setLastUpdatedTimestamp(long lastUpdatedTimestamp) {
this.lastUpdatedTimestamp = lastUpdatedTimestamp;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,35 @@ public void finalizeProcessing() {}
@Override
public void processEvent(Event event) {
String[] lines = event.value.split(System.lineSeparator());
if (lines.length < 2) {
// We expect at-least 2 lines as the first line is always timestamp
if (lines.length < 4) {
// We expect at-least 4 lines as the first line is always timestamp,
// the second line is the list of overridden rca conf values,
// the third line is the timestamp of when the last override was set,
// and there must be at least one ElasticSearch node in a cluster.
LOG.error(
"ClusterDetails contain less items than expected. " + "Expected 2, found: {}",
"ClusterDetails contain less items than expected. " + "Expected 4, found: {}",
event.value);
return;
}

// An example node_metrics data is something like this for a two node cluster:
// {"current_time":1566414001749}
// {"overrides": {"enabled": {}, "disabled": {}}
// {"lastOverrideTimestamp":1566414001749}
// {"ID":"4sqG_APMQuaQwEW17_6zwg","HOST_ADDRESS":"10.212.73.121"}
// {"ID":"OVH94mKXT5ibeqvDoAyTeg","HOST_ADDRESS":"10.212.78.83"}
//
// The line 0 is timestamp that can be skipped. So we allocated size of
// the array is one less than the list.



final List<NodeDetails> tmpNodesDetails = new ArrayList<>();

// Just to keep track of duplicate node ids.
Set<String> ids = new HashSet<>();

for (int i = 1; i < lines.length; ++i) {
for (int i = 3; i < lines.length; ++i) {
NodeDetails nodeDetails = new NodeDetails(lines[i]);

// Include nodeIds we haven't seen so far.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.amazon.opendistro.elasticsearch.performanceanalyzer.config.overrides;

import static org.junit.Assert.assertEquals;

import com.amazon.opendistro.elasticsearch.performanceanalyzer.util.JsonConverter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;

public class ConfigOverridesHelperTests {
private ConfigOverridesWrapper testConfigOverridesWrapper;
private final ConfigOverrides validTestOverrides = buildValidConfigOverrides();
private final String validTestOverrideJson = JsonConverter
.writeValueAsString(validTestOverrides);

@Before
public void setUp() {
testConfigOverridesWrapper = new ConfigOverridesWrapper();
testConfigOverridesWrapper.setCurrentClusterConfigOverrides(validTestOverrides);
}

@Test
public void testSerializeSuccess() throws IOException {
String serializedOverrides = ConfigOverridesHelper.serialize(validTestOverrides);

assertEquals(validTestOverrideJson, serializedOverrides);
}

@Test
public void testDeserializeSuccess() throws IOException {
ConfigOverrides deserializedOverrides =
ConfigOverridesHelper.deserialize(validTestOverrideJson);

assertEquals(validTestOverrides.getEnable().getRcas(), deserializedOverrides.getEnable().getRcas());
assertEquals(validTestOverrides.getEnable().getDeciders(), deserializedOverrides.getEnable().getDeciders());
assertEquals(validTestOverrides.getEnable().getActions(), deserializedOverrides.getEnable().getActions());

assertEquals(validTestOverrides.getDisable().getRcas(), deserializedOverrides.getDisable().getRcas());
assertEquals(validTestOverrides.getDisable().getDeciders(), deserializedOverrides.getDisable().getDeciders());
assertEquals(validTestOverrides.getDisable().getActions(), deserializedOverrides.getDisable().getActions());
}

@Test(expected = IOException.class)
public void testDeserializeIOException() throws IOException {
String nonJsonString = "Not a JSON string.";
ConfigOverridesHelper.deserialize(nonJsonString);
}

private ConfigOverrides buildValidConfigOverrides() {
ConfigOverrides overrides = new ConfigOverrides();
overrides.getDisable().setRcas(Arrays.asList("rca1", "rca2"));
overrides.getDisable().setActions(Arrays.asList("action1", "action2"));
overrides.getEnable().setRcas(Arrays.asList("rca3", "rca4"));
overrides.getEnable().setDeciders(Collections.singletonList("decider1"));

return overrides;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JSeparator;
import org.jooq.tools.json.JSONObject;
import org.junit.After;
import org.junit.Assert;
Expand Down Expand Up @@ -359,17 +360,29 @@ public void testHandlers() throws IOException {
}

private void setMyIp(String ip, AllMetrics.NodeRole nodeRole) {
final String separator = System.lineSeparator();
JSONObject jtime = new JSONObject();
jtime.put("current_time", 1566414001749L);

JSONObject jOverrides = new JSONObject();
JSONObject jOverridesTimeStamp = new JSONObject();

JSONObject jNode = new JSONObject();
jNode.put(AllMetrics.NodeDetailColumns.ID.toString(), "4sqG_APMQuaQwEW17_6zwg");
jNode.put(AllMetrics.NodeDetailColumns.HOST_ADDRESS.toString(), ip);
jNode.put(AllMetrics.NodeDetailColumns.ROLE.toString(), nodeRole);

ClusterDetailsEventProcessor eventProcessor = new ClusterDetailsEventProcessor();
StringBuilder nodeDetails = new StringBuilder();
nodeDetails.append(jtime);
nodeDetails.append(separator);
nodeDetails.append(jOverrides);
nodeDetails.append(separator);
nodeDetails.append(jOverridesTimeStamp);
nodeDetails.append(separator);
nodeDetails.append(jNode.toString());
eventProcessor.processEvent(
new Event("", jtime.toString() + System.lineSeparator() + jNode.toString(), 0));
new Event("", nodeDetails.toString(), 0));
rcaController.getAppContext().setClusterDetailsEventProcessor(eventProcessor);
}

Expand Down
Loading