Skip to content

Commit

Permalink
Forward-port late deprecation info API changes to 8.x (#83675) (#84318)
Browse files Browse the repository at this point in the history
This is a forward-port of #82487, #83544, #83601, #84145, and #84246, but given that the branches had diverged so much they were not a straightforward cherry-picks. It required modifying the interface of the NodeDeprecationChecks to include ClusterState as we do in 7.x.
  • Loading branch information
masseyke committed Feb 23, 2022
1 parent c1f7284 commit e9c0886
Show file tree
Hide file tree
Showing 11 changed files with 1,253 additions and 228 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,26 @@
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Information about deprecated items
*/
public class DeprecationIssue implements Writeable, ToXContentObject {

private static final String ACTIONS_META_FIELD = "actions";
private static final String OBJECTS_FIELD = "objects";
private static final String ACTION_TYPE = "action_type";
private static final String REMOVE_SETTINGS_ACTION_TYPE = "remove_settings";

public enum Level implements Writeable {
/**
* Resolving this issue is advised but not required to upgrade. There may be undesired changes in behavior unless this issue is
Expand Down Expand Up @@ -121,6 +132,10 @@ public Map<String, Object> getMeta() {
return meta;
}

private Optional<Meta> getMetaObject() {
return Meta.fromMetaMap(meta);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
level.writeTo(out);
Expand Down Expand Up @@ -170,4 +185,233 @@ public int hashCode() {
public String toString() {
return Strings.toString(this);
}

public static Map<String, Object> createMetaMapForRemovableSettings(List<String> removableSettings) {
return Meta.fromRemovableSettings(removableSettings).toMetaMap();
}

/**
* This method returns a DeprecationIssue that has in its meta object the intersection of all auto-removable settings that appear on
* all of the DeprecationIssues that are passed in. This method assumes that all DeprecationIssues passed in are equal, except for the
* auto-removable settings in the meta object.
* @param similarIssues DeprecationIssues that are assumed to be identical except possibly removal actions.
* @return A DeprecationIssue containing only the removal actions that are in all similarIssues
*/
public static DeprecationIssue getIntersectionOfRemovableSettings(List<DeprecationIssue> similarIssues) {
if (similarIssues == null || similarIssues.isEmpty()) {
return null;
}
if (similarIssues.size() == 1) {
return similarIssues.get(0);
}
DeprecationIssue representativeIssue = similarIssues.get(0);
Optional<Meta> metaIntersection = similarIssues.stream()
.map(DeprecationIssue::getMetaObject)
.reduce(
representativeIssue.getMetaObject(),
(intersectionSoFar, meta) -> intersectionSoFar.isPresent() && meta.isPresent()
? Optional.of(intersectionSoFar.get().getIntersection(meta.get()))
: Optional.empty()
);
return new DeprecationIssue(
representativeIssue.level,
representativeIssue.message,
representativeIssue.url,
representativeIssue.details,
representativeIssue.resolveDuringRollingUpgrade,
metaIntersection.map(Meta::toMetaMap).orElse(null)
);
}

/*
* This class a represents a DeprecationIssue's meta map. A meta map might look something like:
* {
* "_meta":{
* "foo": "bar",
* "actions":[
* {
* "action_type":"remove_settings",
* "objects":[
* "setting1",
* "setting2"
* ]
* }
* ]
* }
* }
*/
private static final class Meta {
private final List<Action> actions;
private final Map<String, Object> nonActionMetadata;

Meta(List<Action> actions, Map<String, Object> nonActionMetadata) {
this.actions = actions;
this.nonActionMetadata = nonActionMetadata;
}

private static Meta fromRemovableSettings(List<String> removableSettings) {
List<Action> actions;
if (removableSettings == null) {
actions = null;
} else {
actions = Collections.singletonList(new RemovalAction(removableSettings));
}
return new Meta(actions, Collections.emptyMap());
}

private Map<String, Object> toMetaMap() {
Map<String, Object> metaMap;
if (actions != null) {
metaMap = new HashMap<>(nonActionMetadata);
List<Map<String, Object>> actionsList = actions.stream().map(Action::toActionMap).collect(Collectors.toList());
if (actionsList.isEmpty() == false) {
metaMap.put(ACTIONS_META_FIELD, actionsList);
}
} else {
metaMap = nonActionMetadata;
}
return metaMap;
}

/*
* This method gets the intersection of this Meta with another. It assumes that the Meta objects are identical, except possibly the
* contents of the removal actions. So the interection is a new Meta object with only the removal actions that appear in both.
*/
private Meta getIntersection(Meta another) {
final List<Action> actionsIntersection;
if (actions != null && another.actions != null) {
List<Action> combinedActions = this.actions.stream()
.filter(action -> action instanceof RemovalAction == false)
.collect(Collectors.toList());
Optional<Action> thisRemovalAction = this.actions.stream().filter(action -> action instanceof RemovalAction).findFirst();
Optional<Action> otherRemovalAction = another.actions.stream()
.filter(action -> action instanceof RemovalAction)
.findFirst();
if (thisRemovalAction.isPresent() && otherRemovalAction.isPresent()) {
Optional<List<String>> removableSettingsOptional = ((RemovalAction) thisRemovalAction.get()).getRemovableSettings();
List<String> removableSettings = removableSettingsOptional.map(
settings -> settings.stream()
.distinct()
.filter(
setting -> ((RemovalAction) otherRemovalAction.get()).getRemovableSettings()
.map(list -> list.contains(setting))
.orElse(false)
)
.collect(Collectors.toList())
).orElse(Collections.emptyList());
if (removableSettings.isEmpty() == false) {
combinedActions.add(new RemovalAction(removableSettings));
}
}
actionsIntersection = combinedActions;
} else {
actionsIntersection = null;
}
return new Meta(actionsIntersection, nonActionMetadata);
}

/*
* Returns an Optional Meta object from a DeprecationIssue's meta Map. If the meta Map is null then the Optional will not be
* present.
*/
@SuppressWarnings("unchecked")
private static Optional<Meta> fromMetaMap(Map<String, Object> metaMap) {
if (metaMap == null) {
return Optional.empty();
}
List<Map<String, Object>> actionMaps = (List<Map<String, Object>>) metaMap.get(ACTIONS_META_FIELD);
List<Action> actions;
if (actionMaps == null) {
actions = null;
} else {
actions = new ArrayList<>();
for (Map<String, Object> actionMap : actionMaps) {
final Action action;
if (REMOVE_SETTINGS_ACTION_TYPE.equals(actionMap.get(ACTION_TYPE))) {
action = RemovalAction.fromActionMap(actionMap);
} else {
action = UnknownAction.fromActionMap(actionMap);
}
actions.add(action);
}
}
Map<String, Object> nonActionMap = metaMap.entrySet()
.stream()
.filter(entry -> entry.getKey().equals(ACTIONS_META_FIELD) == false)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Meta meta = new Meta(actions, nonActionMap);
return Optional.of(meta);
}
}

/*
* A DeprecationIssue's meta Map optionally has an array of actions. This class reprenents one of the items in that array.
*/
private interface Action {
/*
* This method creates the Map that goes inside the actions list for this Action in a meta Map.
*/
Map<String, Object> toActionMap();
}

/*
* This class a represents remove_settings action within the actions list in a meta Map.
*/
private static final class RemovalAction implements Action {
private final List<String> removableSettings;

RemovalAction(List<String> removableSettings) {
this.removableSettings = removableSettings;
}

@SuppressWarnings("unchecked")
private static RemovalAction fromActionMap(Map<String, Object> actionMap) {
final List<String> removableSettings;
Object removableSettingsObject = actionMap.get(OBJECTS_FIELD);
if (removableSettingsObject == null) {
removableSettings = null;
} else {
removableSettings = (List<String>) removableSettingsObject;
}
return new RemovalAction(removableSettings);
}

private Optional<List<String>> getRemovableSettings() {
return removableSettings == null ? Optional.empty() : Optional.of(removableSettings);
}

@Override
public Map<String, Object> toActionMap() {
final Map<String, Object> actionMap;
if (removableSettings != null) {
actionMap = new HashMap<>();
actionMap.put(OBJECTS_FIELD, removableSettings);
actionMap.put(ACTION_TYPE, REMOVE_SETTINGS_ACTION_TYPE);
} else {
actionMap = null;
}
return actionMap;
}
}

/*
* This represents an action within the actions list in a meta Map that is *not* a removal_action.
*/
private static class UnknownAction implements Action {
private final Map<String, Object> actionMap;

private UnknownAction(Map<String, Object> actionMap) {
this.actionMap = actionMap;
}

private static Action fromActionMap(Map<String, Object> actionMap) {
return new UnknownAction(actionMap);
}

@Override
public Map<String, Object> toActionMap() {
return actionMap;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import org.junit.Before;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.elasticsearch.xcontent.ToXContent.EMPTY_PARAMS;
Expand Down Expand Up @@ -83,4 +86,58 @@ public void testToXContent() throws IOException {
DeprecationIssue other = new DeprecationIssue(Level.fromString(level), message, url, details, requiresRestart, meta);
assertThat(issue, equalTo(other));
}

public void testGetIntersectionOfRemovableSettings() {
assertNull(DeprecationIssue.getIntersectionOfRemovableSettings(null));
assertNull(DeprecationIssue.getIntersectionOfRemovableSettings(Collections.emptyList()));
Map<String, Object> randomMeta = randomMap(1, 5, () -> Tuple.tuple(randomAlphaOfLength(4), randomAlphaOfLength(4)));
DeprecationIssue issue1 = createTestDeprecationIssue(getTestMetaMap(randomMeta, "setting.1", "setting.2", "setting.3"));
assertEquals(issue1, DeprecationIssue.getIntersectionOfRemovableSettings(Collections.singletonList(issue1)));
DeprecationIssue issue2 = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta, "setting.2"));
assertNotEquals(issue1, issue2);
assertEquals(issue2, DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue2)));
DeprecationIssue issue3 = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta, "setting.2", "setting.4"));
assertEquals(issue2, DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue2, issue3)));
DeprecationIssue issue4 = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta, "setting.5"));
DeprecationIssue emptySettingsIssue = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta));
assertEquals(
emptySettingsIssue,
DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue2, issue3, issue4))
);
DeprecationIssue issue5 = createTestDeprecationIssue(getTestMetaMap(randomMeta, "setting.1", "setting.2", "setting.3"));
assertEquals(issue1, DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue5)));
}

private static Map<String, Object> getTestMetaMap(Map<String, Object> baseMap, String... settings) {
Map<String, Object> metaMap = new HashMap<>();
Map<String, Object> settingsMetaMap = DeprecationIssue.createMetaMapForRemovableSettings(
settings.length == 0 ? null : Arrays.asList(settings)
);
metaMap.putAll(settingsMetaMap);
metaMap.putAll(baseMap);
return metaMap;
}

private static DeprecationIssue createTestDeprecationIssue(Map<String, Object> metaMap) {
String details = randomBoolean() ? randomAlphaOfLength(10) : null;
return new DeprecationIssue(
randomFrom(DeprecationIssue.Level.values()),
randomAlphaOfLength(10),
randomAlphaOfLength(10),
details,
randomBoolean(),
metaMap
);
}

private static DeprecationIssue createTestDeprecationIssue(DeprecationIssue seedIssue, Map<String, Object> metaMap) {
return new DeprecationIssue(
seedIssue.getLevel(),
seedIssue.getMessage(),
seedIssue.getUrl(),
seedIssue.getDetails(),
seedIssue.isResolveDuringRollingUpgrade(),
metaMap
);
}
}

0 comments on commit e9c0886

Please sign in to comment.