19
19
20
20
package org .elasticsearch .action .admin .cluster .settings ;
21
21
22
+ import org .apache .logging .log4j .Logger ;
23
+ import org .apache .logging .log4j .message .ParameterizedMessage ;
24
+ import org .apache .logging .log4j .util .Supplier ;
22
25
import org .elasticsearch .cluster .ClusterState ;
23
26
import org .elasticsearch .cluster .block .ClusterBlocks ;
24
27
import org .elasticsearch .cluster .metadata .MetaData ;
28
+ import org .elasticsearch .common .collect .Tuple ;
25
29
import org .elasticsearch .common .settings .ClusterSettings ;
26
30
import org .elasticsearch .common .settings .Settings ;
27
31
32
+ import java .util .Map ;
33
+
28
34
import static org .elasticsearch .cluster .ClusterState .builder ;
35
+ import static org .elasticsearch .common .settings .AbstractScopedSettings .ARCHIVED_SETTINGS_PREFIX ;
29
36
30
37
/**
31
38
* Updates transient and persistent cluster state settings if there are any changes
@@ -48,15 +55,34 @@ synchronized Settings getPersistentUpdate() {
48
55
return persistentUpdates .build ();
49
56
}
50
57
51
- synchronized ClusterState updateSettings (final ClusterState currentState , Settings transientToApply , Settings persistentToApply ) {
58
+ synchronized ClusterState updateSettings (
59
+ final ClusterState currentState , final Settings transientToApply , final Settings persistentToApply , final Logger logger ) {
52
60
boolean changed = false ;
53
- Settings .Builder transientSettings = Settings .builder ();
54
- transientSettings .put (currentState .metaData ().transientSettings ());
55
- changed |= clusterSettings .updateDynamicSettings (transientToApply , transientSettings , transientUpdates , "transient" );
56
61
62
+ /*
63
+ * Our cluster state could have unknown or invalid settings that are known and valid in a previous version of Elasticsearch. We can
64
+ * end up in this situation during a rolling upgrade where the previous version will infect the current version of Elasticsearch
65
+ * with settings that the current version either no longer knows about or now considers to have invalid values. When the current
66
+ * version of Elasticsearch becomes infected with a cluster state containing such settings, we need to skip validating such settings
67
+ * and instead archive them. Consequently, for the current transient and persistent settings in the cluster state we do the
68
+ * following:
69
+ * - split existing settings instance into two with the known and valid settings in one, and the unknown or invalid in another
70
+ * (note that existing archived settings are included in the known and valid settings)
71
+ * - validate the incoming settings update combined with the existing known and valid settings
72
+ * - merge in the archived unknown or invalid settings
73
+ */
74
+ final Tuple <Settings , Settings > partitionedTransientSettings =
75
+ partitionKnownAndValidSettings (currentState .metaData ().transientSettings (), "transient" , logger );
76
+ final Settings knownAndValidTransientSettings = partitionedTransientSettings .v1 ();
77
+ final Settings unknownOrInvalidTransientSettings = partitionedTransientSettings .v2 ();
78
+ final Settings .Builder transientSettings = Settings .builder ().put (knownAndValidTransientSettings );
79
+ changed |= clusterSettings .updateDynamicSettings (transientToApply , transientSettings , transientUpdates , "transient" );
57
80
58
- Settings .Builder persistentSettings = Settings .builder ();
59
- persistentSettings .put (currentState .metaData ().persistentSettings ());
81
+ final Tuple <Settings , Settings > partitionedPersistentSettings =
82
+ partitionKnownAndValidSettings (currentState .metaData ().persistentSettings (), "persistent" , logger );
83
+ final Settings knownAndValidPersistentSettings = partitionedPersistentSettings .v1 ();
84
+ final Settings unknownOrInvalidPersistentSettings = partitionedPersistentSettings .v2 ();
85
+ final Settings .Builder persistentSettings = Settings .builder ().put (knownAndValidPersistentSettings );
60
86
changed |= clusterSettings .updateDynamicSettings (persistentToApply , persistentSettings , persistentUpdates , "persistent" );
61
87
62
88
final ClusterState clusterState ;
@@ -69,8 +95,8 @@ synchronized ClusterState updateSettings(final ClusterState currentState, Settin
69
95
clusterSettings .validate (persistentFinalSettings , true );
70
96
71
97
MetaData .Builder metaData = MetaData .builder (currentState .metaData ())
72
- .persistentSettings ( persistentFinalSettings )
73
- .transientSettings ( transientFinalSettings );
98
+ .transientSettings ( Settings . builder (). put ( transientFinalSettings ). put ( unknownOrInvalidTransientSettings ). build () )
99
+ .persistentSettings ( Settings . builder (). put ( persistentFinalSettings ). put ( unknownOrInvalidPersistentSettings ). build () );
74
100
75
101
ClusterBlocks .Builder blocks = ClusterBlocks .builder ().blocks (currentState .blocks ());
76
102
boolean updatedReadOnly = MetaData .SETTING_READ_ONLY_SETTING .get (metaData .persistentSettings ())
@@ -102,5 +128,46 @@ synchronized ClusterState updateSettings(final ClusterState currentState, Settin
102
128
return clusterState ;
103
129
}
104
130
131
+ /**
132
+ * Partitions the settings into those that are known and valid versus those that are unknown or invalid. The resulting tuple contains
133
+ * the known and valid settings in the first component and the unknown or invalid settings in the second component. Note that archived
134
+ * settings contained in the settings to partition are included in the first component.
135
+ *
136
+ * @param settings the settings to partition
137
+ * @param settingsType a string to identify the settings (for logging)
138
+ * @param logger a logger to sending warnings to
139
+ * @return the partitioned settings
140
+ */
141
+ private Tuple <Settings , Settings > partitionKnownAndValidSettings (
142
+ final Settings settings , final String settingsType , final Logger logger ) {
143
+ final Settings existingArchivedSettings = settings .filter (k -> k .startsWith (ARCHIVED_SETTINGS_PREFIX ));
144
+ final Settings settingsExcludingExistingArchivedSettings =
145
+ settings .filter (k -> k .startsWith (ARCHIVED_SETTINGS_PREFIX ) == false );
146
+ final Settings settingsWithUnknownOrInvalidArchived = clusterSettings .archiveUnknownOrInvalidSettings (
147
+ settingsExcludingExistingArchivedSettings ,
148
+ e -> logUnknownSetting (settingsType , e , logger ),
149
+ (e , ex ) -> logInvalidSetting (settingsType , e , ex , logger ));
150
+ return Tuple .tuple (
151
+ Settings .builder ()
152
+ .put (settingsWithUnknownOrInvalidArchived .filter (k -> k .startsWith (ARCHIVED_SETTINGS_PREFIX ) == false ))
153
+ .put (existingArchivedSettings )
154
+ .build (),
155
+ settingsWithUnknownOrInvalidArchived .filter (k -> k .startsWith (ARCHIVED_SETTINGS_PREFIX )));
156
+ }
157
+
158
+ private void logUnknownSetting (final String settingType , final Map .Entry <String , String > e , final Logger logger ) {
159
+ logger .warn ("ignoring existing unknown {} setting: [{}] with value [{}]; archiving" , settingType , e .getKey (), e .getValue ());
160
+ }
161
+
162
+ private void logInvalidSetting (
163
+ final String settingType , final Map .Entry <String , String > e , final IllegalArgumentException ex , final Logger logger ) {
164
+ logger .warn (
165
+ (Supplier <?>)
166
+ () -> new ParameterizedMessage ("ignoring existing invalid {} setting: [{}] with value [{}]; archiving" ,
167
+ settingType ,
168
+ e .getKey (),
169
+ e .getValue ()),
170
+ ex );
171
+ }
105
172
106
173
}
0 commit comments