Skip to content

Commit e9c0886

Browse files
authored
Forward-port late deprecation info API changes to 8.x (#83675) (#84318)
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.
1 parent c1f7284 commit e9c0886

File tree

11 files changed

+1253
-228
lines changed

11 files changed

+1253
-228
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/deprecation/DeprecationIssue.java

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,26 @@
1515
import org.elasticsearch.xcontent.XContentBuilder;
1616

1717
import java.io.IOException;
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.List;
1822
import java.util.Locale;
1923
import java.util.Map;
2024
import java.util.Objects;
25+
import java.util.Optional;
26+
import java.util.stream.Collectors;
2127

2228
/**
2329
* Information about deprecated items
2430
*/
2531
public class DeprecationIssue implements Writeable, ToXContentObject {
2632

33+
private static final String ACTIONS_META_FIELD = "actions";
34+
private static final String OBJECTS_FIELD = "objects";
35+
private static final String ACTION_TYPE = "action_type";
36+
private static final String REMOVE_SETTINGS_ACTION_TYPE = "remove_settings";
37+
2738
public enum Level implements Writeable {
2839
/**
2940
* Resolving this issue is advised but not required to upgrade. There may be undesired changes in behavior unless this issue is
@@ -121,6 +132,10 @@ public Map<String, Object> getMeta() {
121132
return meta;
122133
}
123134

135+
private Optional<Meta> getMetaObject() {
136+
return Meta.fromMetaMap(meta);
137+
}
138+
124139
@Override
125140
public void writeTo(StreamOutput out) throws IOException {
126141
level.writeTo(out);
@@ -170,4 +185,233 @@ public int hashCode() {
170185
public String toString() {
171186
return Strings.toString(this);
172187
}
188+
189+
public static Map<String, Object> createMetaMapForRemovableSettings(List<String> removableSettings) {
190+
return Meta.fromRemovableSettings(removableSettings).toMetaMap();
191+
}
192+
193+
/**
194+
* This method returns a DeprecationIssue that has in its meta object the intersection of all auto-removable settings that appear on
195+
* all of the DeprecationIssues that are passed in. This method assumes that all DeprecationIssues passed in are equal, except for the
196+
* auto-removable settings in the meta object.
197+
* @param similarIssues DeprecationIssues that are assumed to be identical except possibly removal actions.
198+
* @return A DeprecationIssue containing only the removal actions that are in all similarIssues
199+
*/
200+
public static DeprecationIssue getIntersectionOfRemovableSettings(List<DeprecationIssue> similarIssues) {
201+
if (similarIssues == null || similarIssues.isEmpty()) {
202+
return null;
203+
}
204+
if (similarIssues.size() == 1) {
205+
return similarIssues.get(0);
206+
}
207+
DeprecationIssue representativeIssue = similarIssues.get(0);
208+
Optional<Meta> metaIntersection = similarIssues.stream()
209+
.map(DeprecationIssue::getMetaObject)
210+
.reduce(
211+
representativeIssue.getMetaObject(),
212+
(intersectionSoFar, meta) -> intersectionSoFar.isPresent() && meta.isPresent()
213+
? Optional.of(intersectionSoFar.get().getIntersection(meta.get()))
214+
: Optional.empty()
215+
);
216+
return new DeprecationIssue(
217+
representativeIssue.level,
218+
representativeIssue.message,
219+
representativeIssue.url,
220+
representativeIssue.details,
221+
representativeIssue.resolveDuringRollingUpgrade,
222+
metaIntersection.map(Meta::toMetaMap).orElse(null)
223+
);
224+
}
225+
226+
/*
227+
* This class a represents a DeprecationIssue's meta map. A meta map might look something like:
228+
* {
229+
* "_meta":{
230+
* "foo": "bar",
231+
* "actions":[
232+
* {
233+
* "action_type":"remove_settings",
234+
* "objects":[
235+
* "setting1",
236+
* "setting2"
237+
* ]
238+
* }
239+
* ]
240+
* }
241+
* }
242+
*/
243+
private static final class Meta {
244+
private final List<Action> actions;
245+
private final Map<String, Object> nonActionMetadata;
246+
247+
Meta(List<Action> actions, Map<String, Object> nonActionMetadata) {
248+
this.actions = actions;
249+
this.nonActionMetadata = nonActionMetadata;
250+
}
251+
252+
private static Meta fromRemovableSettings(List<String> removableSettings) {
253+
List<Action> actions;
254+
if (removableSettings == null) {
255+
actions = null;
256+
} else {
257+
actions = Collections.singletonList(new RemovalAction(removableSettings));
258+
}
259+
return new Meta(actions, Collections.emptyMap());
260+
}
261+
262+
private Map<String, Object> toMetaMap() {
263+
Map<String, Object> metaMap;
264+
if (actions != null) {
265+
metaMap = new HashMap<>(nonActionMetadata);
266+
List<Map<String, Object>> actionsList = actions.stream().map(Action::toActionMap).collect(Collectors.toList());
267+
if (actionsList.isEmpty() == false) {
268+
metaMap.put(ACTIONS_META_FIELD, actionsList);
269+
}
270+
} else {
271+
metaMap = nonActionMetadata;
272+
}
273+
return metaMap;
274+
}
275+
276+
/*
277+
* This method gets the intersection of this Meta with another. It assumes that the Meta objects are identical, except possibly the
278+
* contents of the removal actions. So the interection is a new Meta object with only the removal actions that appear in both.
279+
*/
280+
private Meta getIntersection(Meta another) {
281+
final List<Action> actionsIntersection;
282+
if (actions != null && another.actions != null) {
283+
List<Action> combinedActions = this.actions.stream()
284+
.filter(action -> action instanceof RemovalAction == false)
285+
.collect(Collectors.toList());
286+
Optional<Action> thisRemovalAction = this.actions.stream().filter(action -> action instanceof RemovalAction).findFirst();
287+
Optional<Action> otherRemovalAction = another.actions.stream()
288+
.filter(action -> action instanceof RemovalAction)
289+
.findFirst();
290+
if (thisRemovalAction.isPresent() && otherRemovalAction.isPresent()) {
291+
Optional<List<String>> removableSettingsOptional = ((RemovalAction) thisRemovalAction.get()).getRemovableSettings();
292+
List<String> removableSettings = removableSettingsOptional.map(
293+
settings -> settings.stream()
294+
.distinct()
295+
.filter(
296+
setting -> ((RemovalAction) otherRemovalAction.get()).getRemovableSettings()
297+
.map(list -> list.contains(setting))
298+
.orElse(false)
299+
)
300+
.collect(Collectors.toList())
301+
).orElse(Collections.emptyList());
302+
if (removableSettings.isEmpty() == false) {
303+
combinedActions.add(new RemovalAction(removableSettings));
304+
}
305+
}
306+
actionsIntersection = combinedActions;
307+
} else {
308+
actionsIntersection = null;
309+
}
310+
return new Meta(actionsIntersection, nonActionMetadata);
311+
}
312+
313+
/*
314+
* Returns an Optional Meta object from a DeprecationIssue's meta Map. If the meta Map is null then the Optional will not be
315+
* present.
316+
*/
317+
@SuppressWarnings("unchecked")
318+
private static Optional<Meta> fromMetaMap(Map<String, Object> metaMap) {
319+
if (metaMap == null) {
320+
return Optional.empty();
321+
}
322+
List<Map<String, Object>> actionMaps = (List<Map<String, Object>>) metaMap.get(ACTIONS_META_FIELD);
323+
List<Action> actions;
324+
if (actionMaps == null) {
325+
actions = null;
326+
} else {
327+
actions = new ArrayList<>();
328+
for (Map<String, Object> actionMap : actionMaps) {
329+
final Action action;
330+
if (REMOVE_SETTINGS_ACTION_TYPE.equals(actionMap.get(ACTION_TYPE))) {
331+
action = RemovalAction.fromActionMap(actionMap);
332+
} else {
333+
action = UnknownAction.fromActionMap(actionMap);
334+
}
335+
actions.add(action);
336+
}
337+
}
338+
Map<String, Object> nonActionMap = metaMap.entrySet()
339+
.stream()
340+
.filter(entry -> entry.getKey().equals(ACTIONS_META_FIELD) == false)
341+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
342+
343+
Meta meta = new Meta(actions, nonActionMap);
344+
return Optional.of(meta);
345+
}
346+
}
347+
348+
/*
349+
* A DeprecationIssue's meta Map optionally has an array of actions. This class reprenents one of the items in that array.
350+
*/
351+
private interface Action {
352+
/*
353+
* This method creates the Map that goes inside the actions list for this Action in a meta Map.
354+
*/
355+
Map<String, Object> toActionMap();
356+
}
357+
358+
/*
359+
* This class a represents remove_settings action within the actions list in a meta Map.
360+
*/
361+
private static final class RemovalAction implements Action {
362+
private final List<String> removableSettings;
363+
364+
RemovalAction(List<String> removableSettings) {
365+
this.removableSettings = removableSettings;
366+
}
367+
368+
@SuppressWarnings("unchecked")
369+
private static RemovalAction fromActionMap(Map<String, Object> actionMap) {
370+
final List<String> removableSettings;
371+
Object removableSettingsObject = actionMap.get(OBJECTS_FIELD);
372+
if (removableSettingsObject == null) {
373+
removableSettings = null;
374+
} else {
375+
removableSettings = (List<String>) removableSettingsObject;
376+
}
377+
return new RemovalAction(removableSettings);
378+
}
379+
380+
private Optional<List<String>> getRemovableSettings() {
381+
return removableSettings == null ? Optional.empty() : Optional.of(removableSettings);
382+
}
383+
384+
@Override
385+
public Map<String, Object> toActionMap() {
386+
final Map<String, Object> actionMap;
387+
if (removableSettings != null) {
388+
actionMap = new HashMap<>();
389+
actionMap.put(OBJECTS_FIELD, removableSettings);
390+
actionMap.put(ACTION_TYPE, REMOVE_SETTINGS_ACTION_TYPE);
391+
} else {
392+
actionMap = null;
393+
}
394+
return actionMap;
395+
}
396+
}
397+
398+
/*
399+
* This represents an action within the actions list in a meta Map that is *not* a removal_action.
400+
*/
401+
private static class UnknownAction implements Action {
402+
private final Map<String, Object> actionMap;
403+
404+
private UnknownAction(Map<String, Object> actionMap) {
405+
this.actionMap = actionMap;
406+
}
407+
408+
private static Action fromActionMap(Map<String, Object> actionMap) {
409+
return new UnknownAction(actionMap);
410+
}
411+
412+
@Override
413+
public Map<String, Object> toActionMap() {
414+
return actionMap;
415+
}
416+
}
173417
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/deprecation/DeprecationIssueTests.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import org.junit.Before;
1919

2020
import java.io.IOException;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.HashMap;
2124
import java.util.Map;
2225

2326
import static org.elasticsearch.xcontent.ToXContent.EMPTY_PARAMS;
@@ -83,4 +86,58 @@ public void testToXContent() throws IOException {
8386
DeprecationIssue other = new DeprecationIssue(Level.fromString(level), message, url, details, requiresRestart, meta);
8487
assertThat(issue, equalTo(other));
8588
}
89+
90+
public void testGetIntersectionOfRemovableSettings() {
91+
assertNull(DeprecationIssue.getIntersectionOfRemovableSettings(null));
92+
assertNull(DeprecationIssue.getIntersectionOfRemovableSettings(Collections.emptyList()));
93+
Map<String, Object> randomMeta = randomMap(1, 5, () -> Tuple.tuple(randomAlphaOfLength(4), randomAlphaOfLength(4)));
94+
DeprecationIssue issue1 = createTestDeprecationIssue(getTestMetaMap(randomMeta, "setting.1", "setting.2", "setting.3"));
95+
assertEquals(issue1, DeprecationIssue.getIntersectionOfRemovableSettings(Collections.singletonList(issue1)));
96+
DeprecationIssue issue2 = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta, "setting.2"));
97+
assertNotEquals(issue1, issue2);
98+
assertEquals(issue2, DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue2)));
99+
DeprecationIssue issue3 = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta, "setting.2", "setting.4"));
100+
assertEquals(issue2, DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue2, issue3)));
101+
DeprecationIssue issue4 = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta, "setting.5"));
102+
DeprecationIssue emptySettingsIssue = createTestDeprecationIssue(issue1, getTestMetaMap(randomMeta));
103+
assertEquals(
104+
emptySettingsIssue,
105+
DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue2, issue3, issue4))
106+
);
107+
DeprecationIssue issue5 = createTestDeprecationIssue(getTestMetaMap(randomMeta, "setting.1", "setting.2", "setting.3"));
108+
assertEquals(issue1, DeprecationIssue.getIntersectionOfRemovableSettings(Arrays.asList(issue1, issue5)));
109+
}
110+
111+
private static Map<String, Object> getTestMetaMap(Map<String, Object> baseMap, String... settings) {
112+
Map<String, Object> metaMap = new HashMap<>();
113+
Map<String, Object> settingsMetaMap = DeprecationIssue.createMetaMapForRemovableSettings(
114+
settings.length == 0 ? null : Arrays.asList(settings)
115+
);
116+
metaMap.putAll(settingsMetaMap);
117+
metaMap.putAll(baseMap);
118+
return metaMap;
119+
}
120+
121+
private static DeprecationIssue createTestDeprecationIssue(Map<String, Object> metaMap) {
122+
String details = randomBoolean() ? randomAlphaOfLength(10) : null;
123+
return new DeprecationIssue(
124+
randomFrom(DeprecationIssue.Level.values()),
125+
randomAlphaOfLength(10),
126+
randomAlphaOfLength(10),
127+
details,
128+
randomBoolean(),
129+
metaMap
130+
);
131+
}
132+
133+
private static DeprecationIssue createTestDeprecationIssue(DeprecationIssue seedIssue, Map<String, Object> metaMap) {
134+
return new DeprecationIssue(
135+
seedIssue.getLevel(),
136+
seedIssue.getMessage(),
137+
seedIssue.getUrl(),
138+
seedIssue.getDetails(),
139+
seedIssue.isResolveDuringRollingUpgrade(),
140+
metaMap
141+
);
142+
}
86143
}

0 commit comments

Comments
 (0)