Skip to content

Commit 9ef81b2

Browse files
authored
Feature gates (#2361)
Feature gates
1 parent 3a737ef commit 9ef81b2

File tree

16 files changed

+240
-19
lines changed

16 files changed

+240
-19
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) 2021, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
4+
package oracle.kubernetes.json;
5+
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
import static java.lang.annotation.ElementType.FIELD;
11+
12+
/** Specifies that this field is related to the named feature. */
13+
@Retention(RetentionPolicy.RUNTIME)
14+
@Target(FIELD)
15+
public @interface Feature {
16+
17+
/**
18+
* Feature name.
19+
*
20+
* @return feature name
21+
*/
22+
String value();
23+
}

json-schema-generator/src/main/java/oracle/kubernetes/json/SchemaGenerator.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public class SchemaGenerator {
6464
private final Collection<String> suppressDescriptionForPackages = new ArrayList<>();
6565
private final Map<Class<?>, String> additionalPropertiesTypes = new HashMap<>();
6666

67+
private final Collection<String> enabledFeatures = new ArrayList<>();
68+
6769
/**
6870
* Returns a pretty-printed string corresponding to a generated schema.
6971
*
@@ -195,7 +197,7 @@ void generateFieldIn(Map<String, Object> map, Field field) {
195197
}
196198

197199
private boolean includeInSchema(Field field) {
198-
return !isStatic(field) && !isVolatile(field);
200+
return !isStatic(field) && !isVolatile(field) && !isDisabledFeature(field);
199201
}
200202

201203
private boolean isStatic(Field field) {
@@ -210,6 +212,11 @@ private boolean isDeprecated(Field field) {
210212
return field.getAnnotation(Deprecated.class) != null;
211213
}
212214

215+
private boolean isDisabledFeature(Field field) {
216+
Feature feature = field.getAnnotation(Feature.class);
217+
return feature != null && !enabledFeatures.contains(feature.value());
218+
}
219+
213220
private String getPropertyName(Field field) {
214221
SerializedName serializedName = field.getAnnotation(SerializedName.class);
215222
if (serializedName != null && serializedName.value().length() > 0) {
@@ -502,6 +509,10 @@ public void defineAdditionalProperties(Class<?> forClass, String additionalPrope
502509
additionalPropertiesTypes.put(forClass, additionalPropertyType);
503510
}
504511

512+
public void defineEnabledFeatures(Collection<String> enabledFeatures) {
513+
this.enabledFeatures.addAll(enabledFeatures);
514+
}
515+
505516
private class SubSchemaGenerator {
506517
final Field field;
507518

json-schema-generator/src/test/java/oracle/kubernetes/json/SchemaGeneratorTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.lang.reflect.Field;
88
import java.net.URL;
99
import java.util.HashMap;
10+
import java.util.List;
1011
import java.util.Map;
1112

1213
import org.junit.jupiter.api.BeforeEach;
@@ -215,6 +216,22 @@ public void doNotGenerateSchemaForStatics() {
215216
assertThat(schema, hasJsonPath("$.required", not(arrayContaining("staticInt"))));
216217
}
217218

219+
@Test
220+
public void generateSchemaForEnabledFeature() throws NoSuchFieldException {
221+
generator.defineEnabledFeatures(List.of("Binding"));
222+
Object schema = generator.generate(SimpleObject.class);
223+
224+
assertThat(schema, hasJsonPath("$.properties.fieldAssociatedWithBindingFeature"));
225+
}
226+
227+
@Test
228+
public void doNotGenerateSchemaForDisabledFeature() {
229+
Object schema = generator.generate(SimpleObject.class);
230+
231+
assertThat(schema, hasNoJsonPath("$.properties.fieldAssociatedWithMountFeature"));
232+
assertThat(schema, hasJsonPath("$.required", not(arrayContaining("fieldAssociatedWithMountFeature"))));
233+
}
234+
218235
@SuppressWarnings("unused")
219236
@Test
220237
public void generateSchemaForSimpleObject() {

json-schema-generator/src/test/java/oracle/kubernetes/json/SimpleObject.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,10 @@ class SimpleObject {
2323
private float aaFloat;
2424

2525
private Map<String,Integer> keys;
26+
27+
@Feature("Mount")
28+
private String fieldAssociatedWithMountFeature;
29+
30+
@Feature("Binding")
31+
private String fieldAssociatedWithBindingFeature;
2632
}

kubernetes/charts/weblogic-operator/templates/_operator-cm.tpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ data:
3434
{{- if .dns1123Fields }}
3535
dns1123Fields: {{ .dns1123Fields | quote }}
3636
{{- end }}
37+
{{- if .featureGates }}
38+
featureGates: {{ .featureGates | quote }}
39+
{{- end }}
3740
{{- if .introspectorJobNameSuffix }}
3841
introspectorJobNameSuffix: {{ .introspectorJobNameSuffix | quote }}
3942
{{- end }}

kubernetes/charts/weblogic-operator/values.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ elasticSearchHost: "elasticsearch.default.svc.cluster.local"
117117
# This parameter is ignored if 'elkIntegrationEnabled' is false.
118118
elasticSearchPort: 9200
119119

120+
# featureGates specifies a set of key=value pairs separated by commas that describe whether a given
121+
# operator feature is enabled. You enable a feature by including a key=value pair where the key is the
122+
# feature name and the value is "true". This will allow the operator team to release features that
123+
# are not yet ready to be enabled by default, but that are ready for testing by customers. Once a feature is
124+
# stable then it will be enabled by default and can not be disabled using this configuration.
125+
# featureGates: "...,CommonMounts=true"
126+
120127
# javaLoggingLevel specifies the Java logging level for the operator. This affects the operator pod's
121128
# log output and the contents of log files in the container's /logs/ directory.
122129
# Valid values are: "SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", and "FINEST".

operator/src/main/java/oracle/kubernetes/operator/Main.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ private static String getBuildProperty(Properties buildProps, String key) {
175175

176176
private void logStartup(LoggingFacade loggingFacade) {
177177
loggingFacade.info(MessageKeys.OPERATOR_STARTED, buildVersion, operatorImpl, operatorBuildTime);
178+
Optional.ofNullable(TuningParameters.getInstance().getFeatureGates().getEnabledFeatures())
179+
.ifPresent(ef -> loggingFacade.info(MessageKeys.ENABLED_FEATURES, ef));
178180
loggingFacade.info(MessageKeys.OP_CONFIG_NAMESPACE, getOperatorNamespace());
179181
loggingFacade.info(MessageKeys.OP_CONFIG_SERVICE_ACCOUNT, serviceAccountName);
180182
Optional.ofNullable(Namespaces.getConfiguredDomainNamespaces())

operator/src/main/java/oracle/kubernetes/operator/TuningParameters.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
package oracle.kubernetes.operator;
55

6+
import java.util.Collection;
7+
import java.util.Collections;
68
import java.util.Map;
79
import java.util.concurrent.ScheduledExecutorService;
810

@@ -29,6 +31,8 @@ static TuningParameters getInstance() {
2931

3032
PodTuning getPodTuning();
3133

34+
FeatureGates getFeatureGates();
35+
3236
class MainTuning {
3337
public final int initializationRetryDelaySeconds;
3438
public final int domainPresenceFailureRetrySeconds;
@@ -314,4 +318,48 @@ public boolean equals(Object o) {
314318
.isEquals();
315319
}
316320
}
321+
322+
class FeatureGates {
323+
public final Collection<String> enabledFeatures;
324+
325+
public FeatureGates(Collection<String> enabledFeatures) {
326+
this.enabledFeatures = Collections.unmodifiableCollection(enabledFeatures);
327+
}
328+
329+
public Collection<String> getEnabledFeatures() {
330+
return enabledFeatures;
331+
}
332+
333+
public boolean isFeatureEnabled(String featureName) {
334+
return enabledFeatures.contains(featureName);
335+
}
336+
337+
@Override
338+
public String toString() {
339+
return new ToStringBuilder(this)
340+
.append("enabledFeatures", enabledFeatures)
341+
.toString();
342+
}
343+
344+
@Override
345+
public int hashCode() {
346+
return new HashCodeBuilder()
347+
.append(enabledFeatures)
348+
.toHashCode();
349+
}
350+
351+
@Override
352+
public boolean equals(Object o) {
353+
if (o == null) {
354+
return false;
355+
}
356+
if (!(o instanceof FeatureGates)) {
357+
return false;
358+
}
359+
FeatureGates fg = (FeatureGates) o;
360+
return new EqualsBuilder()
361+
.append(enabledFeatures, fg.enabledFeatures)
362+
.isEquals();
363+
}
364+
}
317365
}

operator/src/main/java/oracle/kubernetes/operator/TuningParametersImpl.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33

44
package oracle.kubernetes.operator;
55

6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.Collection;
69
import java.util.concurrent.ScheduledExecutorService;
710
import java.util.concurrent.locks.ReadWriteLock;
811
import java.util.concurrent.locks.ReentrantReadWriteLock;
12+
import java.util.stream.Collectors;
913

1014
import oracle.kubernetes.operator.helpers.ConfigMapConsumer;
1115
import oracle.kubernetes.operator.logging.LoggingFacade;
@@ -23,6 +27,7 @@ public class TuningParametersImpl extends ConfigMapConsumer implements TuningPar
2327
private CallBuilderTuning callBuilder = null;
2428
private WatchTuning watch = null;
2529
private PodTuning pod = null;
30+
private FeatureGates featureGates = null;
2631

2732
private TuningParametersImpl(ScheduledExecutorService executorService) {
2833
super(executorService);
@@ -82,23 +87,40 @@ private void update() {
8287
(int) readTuningParameter("livenessProbePeriodSeconds", 45),
8388
readTuningParameter("introspectorJobActiveDeadlineSeconds", 120));
8489

90+
FeatureGates featureGates =
91+
new FeatureGates(generateFeatureGates(get("featureGates")));
92+
8593
lock.writeLock().lock();
8694
try {
8795
if (!main.equals(this.main)
8896
|| !callBuilder.equals(this.callBuilder)
8997
|| !watch.equals(this.watch)
90-
|| !pod.equals(this.pod)) {
98+
|| !watch.equals(this.pod)
99+
|| !pod.equals(this.featureGates)) {
91100
LOGGER.info(MessageKeys.TUNING_PARAMETERS);
92101
}
93102
this.main = main;
94103
this.callBuilder = callBuilder;
95104
this.watch = watch;
96105
this.pod = pod;
106+
this.featureGates = featureGates;
97107
} finally {
98108
lock.writeLock().unlock();
99109
}
100110
}
101111

112+
private Collection<String> generateFeatureGates(String featureGatesProperty) {
113+
Collection<String> enabledGates = new ArrayList<>();
114+
if (featureGatesProperty != null) {
115+
Arrays.stream(
116+
featureGatesProperty.split(","))
117+
.filter(s -> s.endsWith("=true"))
118+
.map(s -> s.substring(s.indexOf('=')))
119+
.collect(Collectors.toCollection(() -> enabledGates));
120+
}
121+
return enabledGates;
122+
}
123+
102124
@Override
103125
public MainTuning getMainTuning() {
104126
lock.readLock().lock();
@@ -138,4 +160,14 @@ public PodTuning getPodTuning() {
138160
lock.readLock().unlock();
139161
}
140162
}
163+
164+
@Override
165+
public FeatureGates getFeatureGates() {
166+
lock.readLock().lock();
167+
try {
168+
return featureGates;
169+
} finally {
170+
lock.readLock().unlock();
171+
}
172+
}
141173
}

operator/src/main/java/oracle/kubernetes/operator/logging/MessageKeys.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class MessageKeys {
1111
public static final String OPERATOR_STARTED = "WLSKO-0000";
1212
public static final String CREATING_API_CLIENT = "WLSKO-0001";
1313
public static final String K8S_MASTER_URL = "WLSKO-0002";
14+
public static final String ENABLED_FEATURES = "WLSKO-0003";
1415
public static final String OPERATOR_SHUTTING_DOWN = "WLSKO-0005";
1516
public static final String EXCEPTION = "WLSKO-0006";
1617
public static final String CREATING_CRD = "WLSKO-0012";
@@ -19,11 +20,9 @@ public class MessageKeys {
1920
public static final String SECRET_DATA_NOT_FOUND = "WLSKO-0020";
2021
public static final String WLS_CONFIGURATION_READ = "WLSKO-0021";
2122
public static final String JSON_PARSING_FAILED = "WLSKO-0026";
22-
public static final String SERVICE_URL = "WLSKO-0027";
2323
public static final String NO_WLS_SERVER_IN_CLUSTER = "WLSKO-0028";
2424
public static final String VERIFY_ACCESS_START = "WLSKO-0029";
2525
public static final String VERIFY_ACCESS_DENIED = "WLSKO-0030";
26-
public static final String NAMESPACE_IS_DEFAULT = "WLSKO-0031";
2726
public static final String STARTING_LIVENESS_THREAD = "WLSKO-0034";
2827
public static final String COULD_NOT_CREATE_LIVENESS_FILE = "WLSKO-0035";
2928
public static final String REST_AUTHENTICATION_MISSING_ACCESS_TOKEN = "WLSKO-0037";
@@ -138,7 +137,6 @@ public class MessageKeys {
138137
public static final String CLUSTER_PDB_PATCHED = "WLSKO-0185";
139138
public static final String BEGIN_MANAGING_NAMESPACE = "WLSKO-0186";
140139
public static final String END_MANAGING_NAMESPACE = "WLSKO-0187";
141-
public static final String MII_DOMAIN_DYNAMICALLY_UPDATED = "WLSKO-0188";
142140
public static final String HTTP_REQUEST_GOT_THROWABLE = "WLSKO-0189";
143141

144142
// domain status messages

0 commit comments

Comments
 (0)