From 89d3441bb04047d63ff8f4f586773d7c287e1deb Mon Sep 17 00:00:00 2001 From: Daniel Cruz Date: Fri, 22 Mar 2019 08:33:29 -0500 Subject: Add MDC Topic Filtering A feature to provide configurable properties for network topics to extrac fields from JSON strings and place them in a mapped diagnostic context. Issue-ID: POLICY-1499 Change-Id: Icfca0be3b263ccf1612b79ed617e2b1ffb0317e6 Signed-off-by: Daniel Cruz --- .../feature/config/feature-mdc-filters.properties | 54 ++++ .../src/main/feature/install/disable | 32 ++ .../src/main/feature/install/enable | 32 ++ .../drools/mdc/filters/MdcFilterFeature.java | 201 ++++++++++++ .../policy/drools/mdc/filters/MdcTopicFilter.java | 337 +++++++++++++++++++++ ...y.common.endpoints.features.NetLoggerFeatureApi | 1 + ...licy.drools.features.PolicyControllerFeatureAPI | 1 + 7 files changed, 658 insertions(+) create mode 100755 feature-mdc-filters/src/main/feature/config/feature-mdc-filters.properties create mode 100755 feature-mdc-filters/src/main/feature/install/disable create mode 100755 feature-mdc-filters/src/main/feature/install/enable create mode 100755 feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcFilterFeature.java create mode 100755 feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcTopicFilter.java create mode 100755 feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.common.endpoints.features.NetLoggerFeatureApi create mode 100755 feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureAPI (limited to 'feature-mdc-filters/src/main') diff --git a/feature-mdc-filters/src/main/feature/config/feature-mdc-filters.properties b/feature-mdc-filters/src/main/feature/config/feature-mdc-filters.properties new file mode 100755 index 00000000..0a53a9f5 --- /dev/null +++ b/feature-mdc-filters/src/main/feature/config/feature-mdc-filters.properties @@ -0,0 +1,54 @@ +### +# ============LICENSE_START======================================================= +# feature-mdc-filters +# ================================================================================ +# Copyright (C) 2019 AT&T Intellectual Property. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============LICENSE_END========================================================= +### + +# The properties keys follow the controller topic configurations followed by a +# a new topic property, 'mdcFilters'. +#..topics..mdcFilters + +# The value of the property is broken down to the MDC key name to be used by the +# feature followed by the path(s) to the desired field's value. +#dmaap.sink.topics.example.mdcFilters=sampleKey=$.path.to.sample.key + +# The path always begins with '$' as this signifies the root of the JSON document. +# The underlying library used is Jayway JsonPath. The library's query syntax is +# supported for searching a JSON document. The query syntax and some examples +# can be found at: https://github.com/json-path/JsonPath + +# Multiple fields can be found for a given JSON document by a comma separated list +# of pairs. +#dmaap.sink.topics.example.mdcFilters=field1=$.field1,field2=$.field2 + +# If a given topic supports multiple message types that have fields with the same +# name, a '|' separated list can define multiple paths to a field. The feature +# will loop through each path until it finds a match and returns it. +#dmaap.sink.topics.example.mdcFilters=field1=$.field1|$.body.field1 + +# dmaap source filters +dmaap.source.topics.PDPD-CONFIGURATION.mdcFilters=requestID=$.requestID +dmaap.source.topics.DCAE_TOPIC.mdcFilters=requestID=$.requestID +dmaap.source.topics.APPC-CL.mdcFilters=requestID=$.CommonHeader.RequestID +dmaap.source.topics.APPC-LCM-WRITE.mdcFilters=requestID=$.body.output.common-header.request-id +dmaap.source.topics.SDNR-CL-RSP.mdcFilters=requestID=$.body.CommonHeader.RequestID + +# dmaap sink filters +dmaap.sink.topics.POLICY-CL-MGT.mdcFilters=requestID=$.requestID +dmaap.sink.topics.APPC-CL.mdcFilters=requestID=$.CommonHeader.RequestID +dmaap.sink.topics.APPC-LCM-READ.mdcFilters=requestID=$.body.input.common-header.request-id +dmaap.sink.topics.SDNR-CL.mdcFilters=requestID=$.body.CommonHeader.RequestID diff --git a/feature-mdc-filters/src/main/feature/install/disable b/feature-mdc-filters/src/main/feature/install/disable new file mode 100755 index 00000000..25d57374 --- /dev/null +++ b/feature-mdc-filters/src/main/feature/install/disable @@ -0,0 +1,32 @@ +#!/bin/bash + +### +# ============LICENSE_START======================================================= +# feature-mdc-filters +# ================================================================================ +# Copyright (C) 2019 AT&T Intellectual Property. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============LICENSE_END========================================================= +## + +if [[ "${DEBUG}" == "y" ]]; then + set -x +fi + +CONFIG_DIR="${POLICY_HOME}"/config +for mainConfig in ${CONFIG_DIR}/logback.xml ${CONFIG_DIR}/logback-eelf.xml; do + if [ -e "${mainConfig}" ]; then + sed -i --follow-symlinks 's/${abstractNetworkPattern}/${networkPattern}/' "${mainConfig}" + fi +done diff --git a/feature-mdc-filters/src/main/feature/install/enable b/feature-mdc-filters/src/main/feature/install/enable new file mode 100755 index 00000000..57c7cc4a --- /dev/null +++ b/feature-mdc-filters/src/main/feature/install/enable @@ -0,0 +1,32 @@ +#!/bin/bash + +### +# ============LICENSE_START======================================================= +# feature-mdc-filters +# ================================================================================ +# Copyright (C) 2019 AT&T Intellectual Property. 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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# ============LICENSE_END========================================================= +## + +if [[ "${DEBUG}" == "y" ]]; then + set -x +fi + +CONFIG_DIR="${POLICY_HOME}"/config +for mainConfig in ${CONFIG_DIR}/logback.xml ${CONFIG_DIR}/logback-eelf.xml; do + if [ -e "${mainConfig}" ]; then + sed -i --follow-symlinks 's/${networkPattern}/${abstractNetworkPattern}/' "${mainConfig}" + fi +done diff --git a/feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcFilterFeature.java b/feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcFilterFeature.java new file mode 100755 index 00000000..369c0aa0 --- /dev/null +++ b/feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcFilterFeature.java @@ -0,0 +1,201 @@ +/* + * ============LICENSE_START======================================================= + * feature-mdc-filters + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.drools.mdc.filters; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.onap.policy.common.endpoints.event.comm.Topic; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.features.NetLoggerFeatureApi; +import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType; +import org.onap.policy.drools.features.PolicyControllerFeatureAPI; +import org.onap.policy.drools.persistence.SystemPersistence; +import org.onap.policy.drools.system.PolicyController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +public class MdcFilterFeature implements NetLoggerFeatureApi, PolicyControllerFeatureAPI { + + /** + * Logger. + */ + private static final Logger logger = LoggerFactory.getLogger(MdcFilterFeature.class); + + /** + * Feature properties. + */ + public static final String FEATURE_NAME = "feature-mdc-filters"; + public static final String SOURCE = "source"; + public static final String SINK = "sink"; + public static final String MDC_FILTERS = ".mdcFilters"; + + /** + * Mapping of 'protocol:type:topic' key to a 'MdcTopicFilter' object. + */ + private Map topicFilters = new HashMap<>(); + + /** + * Feature properties map obtained from the feature properties file. + */ + private Properties featureProps = null; + + /** + * Constructor. + */ + public MdcFilterFeature() { + super(); + featureProps = getFeatureProps(); + } + + /** + * Gets the feature properties. + * + * @return the properties for this feature. + */ + protected Properties getFeatureProps() { + return SystemPersistence.manager.getProperties(FEATURE_NAME); + } + + /** + * Sequence number to be used for order of feature implementer execution. + */ + @Override + public int getSequenceNumber() { + return 1; + } + + /** + * Loops through all source and sink topics to find which topics have mdc filters and + * inserts an MdcTopicFilter in to the topicFilters map. + */ + @Override + public boolean afterCreate(PolicyController controller) { + createSourceTopicFilters(controller); + createSinkTopicFilters(controller); + return false; + } + + /** + * Extracts the fields in a JSON string that are to be logged in an abbreviated + * message. The event delivery infrastructure details are put in the MDC as well using + * the keys networkEventType (IN/OUT), networkProtocol (UEB/DMAAP/NOOP/REST), and + * networkTopic. + */ + @Override + public boolean beforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic, + String message) { + + String filterKey = null; + if (type == EventType.IN) { + filterKey = getTopicKey(protocol.name().toLowerCase(), SOURCE, topic); + } else { + filterKey = getTopicKey(protocol.name().toLowerCase(), SINK, topic); + } + + MDC.put("networkEventType", type.name()); + MDC.put("networkProtocol", protocol.name()); + MDC.put("networkTopic", topic); + + MdcTopicFilter filter = topicFilters.get(filterKey); + if (filter != null) { + for (Map.Entry> entry : filter.find(message).entrySet()) { + String mdcKey = entry.getKey(); + List results = entry.getValue(); + if (results.isEmpty()) { + logger.debug("No results found for key {}", mdcKey); + } else if (results.size() > 1) { + logger.debug("Multple results found for key {}, returning list as a string", mdcKey); + MDC.put(mdcKey, results.toString()); + } else { + MDC.put(mdcKey, results.get(0)); + } + } + } else { + logger.debug("No mdc topic filters exist for key {}", filterKey); + } + + return false; + } + + /** + * Clears the MDC mapping after a message is logged. + */ + @Override + public boolean afterLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic, + String message) { + MDC.clear(); + return false; + } + + /** + * Creates a key using the protocol, type, and topic name. + * + * @param protocol defined as ueb, dmaap, noop + * @param type defined as source or sink + * @param topic name of the topic + * @return a key that is the concatenation of the protocol, type, and topic name + */ + private String getTopicKey(String protocol, String type, String topic) { + return protocol + ":" + type + ":" + topic; + } + + /** + * Creates MdcTopicFilters for a source/sink topic based on the type. + * + * @param topic the topic name + * @param type 'source' or 'sink' + */ + private void createTopicFilter(Topic topic, String type) { + String protocol = topic.getTopicCommInfrastructure().name().toLowerCase(); + String topicName = topic.getTopic(); + + String propertyKey = protocol + "." + type + ".topics." + topicName + MDC_FILTERS; + String propertyValue = featureProps.getProperty(propertyKey); + if (propertyValue != null) { + String topicKey = getTopicKey(protocol, type, topicName); + if (!topicFilters.containsKey(topicKey)) { + logger.debug("MdcTopicFilter created for {} {} topic {}", protocol, type, topicName); + topicFilters.put(topicKey, new MdcTopicFilter(propertyValue)); + } else { + logger.debug("An MdcTopicFilter already exists for key {}", topicKey); + } + } else { + logger.debug("No MDC filters defined for {} {} topic {}", protocol, type, topicName); + } + } + + /** + * Creates MdcTopicFilters for the controller's source topics. + */ + private void createSourceTopicFilters(PolicyController controller) { + controller.getTopicSources().forEach(sourceTopic -> createTopicFilter(sourceTopic, SOURCE)); + } + + /** + * Creates MdcTopicFilters for the controller's sink topics. + */ + private void createSinkTopicFilters(PolicyController controller) { + controller.getTopicSinks().forEach(sinkTopic -> createTopicFilter(sinkTopic, SINK)); + } +} diff --git a/feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcTopicFilter.java b/feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcTopicFilter.java new file mode 100755 index 00000000..d0813a93 --- /dev/null +++ b/feature-mdc-filters/src/main/java/org/onap/policy/drools/mdc/filters/MdcTopicFilter.java @@ -0,0 +1,337 @@ +/* + * ============LICENSE_START======================================================= + * feature-mdc-filters + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.drools.mdc.filters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.onap.policy.drools.protocol.coders.JsonProtocolFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MdcTopicFilter { + + private static final Logger logger = LoggerFactory.getLogger(MdcTopicFilter.class); + + public static final String MDC_KEY_ERROR = "mdcKey must be provided"; + public static final String JSON_PATH_ERROR = "json path(s) must be provided"; + + private Map rules = new HashMap<>(); + + public static class FilterRule { + private String mdcKey; + private List paths; + + public FilterRule(String mdcKey, String path) { + this.mdcKey = mdcKey; + this.paths = Arrays.asList(path); + } + + /** + * Constructor. + * + * @param mdcKey the key to the filter rule + * @param paths the list of potential paths to the key + */ + public FilterRule(String mdcKey, List paths) { + this.mdcKey = mdcKey; + this.paths = paths; + } + + public String getMdcKey() { + return mdcKey; + } + + public List getPaths() { + return paths; + } + + protected void setMdcKey(String mdcKey) { + if (mdcKey == null || mdcKey.isEmpty()) { + throw new IllegalArgumentException(MDC_KEY_ERROR); + } + this.mdcKey = mdcKey; + } + + protected void setPaths(List paths) { + if (paths == null || paths.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + this.paths = paths; + } + + protected void addPaths(List paths) { + if (paths == null || paths.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + this.paths.addAll(paths); + } + + protected void addPath(String path) { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + this.paths.add(path); + } + } + + protected MdcTopicFilter(String rawFilters) { + for (String filter : rawFilters.split("\\s*,\\s*")) { + FilterRule rule = createFilterRule(filter); + rules.put(rule.mdcKey, rule); + } + } + + private FilterRule createFilterRule(String filter) { + String[] filterKeyPaths = filter.split("\\s*=\\s*"); + if (filterKeyPaths.length != 2) { + throw new IllegalArgumentException("could not parse filter rule"); + } + + String filterKey = filterKeyPaths[0]; + String paths = filterKeyPaths[1]; + List filterPaths = new ArrayList<>(Arrays.asList(paths.split("(? getFilterRule() { + return new ArrayList<>(rules.values()); + } + + /** + * Gets the filter rule for the specified key. + * + * @param mdcKey the key to the filter rule + * @return the filter rule associated with the key + */ + protected FilterRule getFilterRule(String mdcKey) { + if (mdcKey == null || mdcKey.isEmpty()) { + throw new IllegalArgumentException(MDC_KEY_ERROR); + } + return rules.get(mdcKey); + } + + /** + * Adds a filter rule for the specified key and path. + * + * @param mdcKey the key to the filter rule + * @param path the json path to the key + * @return the filter rule that was added for the topic + */ + protected FilterRule addFilterRule(String mdcKey, String path) { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + return addFilterRule(mdcKey, Arrays.asList(path)); + } + + /** + * Adds a filter rule for the specified key and paths. + * + * @param mdcKey the key to the filter rule + * @param paths the list of potential paths to the key + * @return the filter rule that was added for the topic + */ + protected FilterRule addFilterRule(String mdcKey, List paths) { + if (mdcKey == null || mdcKey.isEmpty()) { + throw new IllegalArgumentException(MDC_KEY_ERROR); + } + + if (paths == null || paths.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + + if (rules.containsKey(mdcKey)) { + throw new IllegalArgumentException("a filter rule already exists for key: " + mdcKey); + } + + FilterRule rule = new FilterRule(mdcKey, paths); + rules.put(mdcKey, rule); + return rule; + } + + /** + * Modifies an existing filter rule by adding the specified path. + * + * @param mdcKey the key to the filter rule + * @param path the path to the key + * @return the filter rule that was modified + */ + protected FilterRule modifyFilterRule(String mdcKey, String path) { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + return modifyFilterRule(mdcKey, Arrays.asList(path)); + } + + /** + * Modifies an existing filter rule by adding the specified paths. + * + * @param mdcKey the key to the filter rule + * @param paths the list of potential paths to the key + * @return the filter rule that was modified + */ + protected FilterRule modifyFilterRule(String mdcKey, List paths) { + if (mdcKey == null || mdcKey.isEmpty()) { + throw new IllegalArgumentException(MDC_KEY_ERROR); + } + + if (paths == null || paths.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + + if (!rules.containsKey(mdcKey)) { + throw new IllegalArgumentException("a filter rule doesn't exist for key: " + mdcKey); + } + + FilterRule rule = rules.get(mdcKey); + rule.addPaths(paths); + return rule; + } + + /** + * Modifies an existing filter rule's key and replaces the paths with the specified + * paths. + * + * @param oldMdcKey the old key to the filter rule + * @param newMdcKey the new key to the filter rule + * @param paths the list of potential paths to the key + * @return the filter rule that was modified + */ + protected FilterRule modifyFilterRule(String oldMdcKey, String newMdcKey, List paths) { + if (oldMdcKey == null || oldMdcKey.isEmpty()) { + throw new IllegalArgumentException("current mdcKey must be provided"); + } + + if (newMdcKey == null || newMdcKey.isEmpty()) { + throw new IllegalArgumentException("new mdcKey must be provided"); + } + + if (oldMdcKey.equals(newMdcKey)) { + throw new IllegalArgumentException("the old and new mdcKey are equivalent"); + } + if (paths == null || paths.isEmpty()) { + throw new IllegalArgumentException(JSON_PATH_ERROR); + } + + if (rules.containsKey(newMdcKey)) { + throw new IllegalArgumentException("a filter rule already exists for key: " + newMdcKey); + } + + FilterRule rule = rules.remove(oldMdcKey); + if (rule == null) { + throw new IllegalArgumentException("a filter rule doesn't exist for key: " + oldMdcKey); + } + + rule.setMdcKey(newMdcKey); + rule.setPaths(paths); + rules.put(newMdcKey, rule); + return rule; + } + + /** + * Deletes all filter rules for the topic filter. + */ + protected void deleteFilterRule() { + rules.clear(); + } + + /** + * Deletes an existing filter rule. + * + * @param mdcKey the key to the filter rule + * @return the filter rule that was deleted + */ + protected FilterRule deleteFilterRule(String mdcKey) { + if (mdcKey == null || mdcKey.isEmpty()) { + throw new IllegalArgumentException(MDC_KEY_ERROR); + } + return rules.remove(mdcKey); + } + + /** + * Finds all fields for each topic filter rule. The results are stored in a map that + * is indexed by the MDC key. Each MDC key has a list of results as multiple + * occurrences of a key can be found in a JSON document. + * + * @param json the json string to be parsed + * @return a map of mdc keys and list of results for each key + */ + protected Map> find(String json) { + Map> results = new HashMap<>(); + for (FilterRule rule : rules.values()) { + List matches = new ArrayList<>(); + for (String path : rule.getPaths()) { + + try { + matches = JsonProtocolFilter.filter(json, path); + } catch (Exception e) { + logger.debug("Could not filter on path {} because of {}", path, e.getMessage(), e); + } + + if (!matches.isEmpty()) { + break; + } else { + logger.error("Could not find path {} in json {}", path, json); + } + + } + results.put(rule.getMdcKey(), matches); + } + return results; + } + + /** + * Finds all occurrences of a field in a JSON document based on the filter rule paths. + * + * @param json the json string to be parsed + * @return a list of matches from the JSON document + */ + protected List find(String json, String mdcKey) { + List matches = new ArrayList<>(); + for (String path : rules.get(mdcKey).getPaths()) { + + try { + matches = JsonProtocolFilter.filter(json, path); + } catch (Exception e) { + logger.debug("Could not filter on path {} because of {}", path, e.getMessage(), e); + } + + if (!matches.isEmpty()) { + break; + } + + } + + if (matches.isEmpty()) { + logger.error("Could not find any matches for key {} in json {}", mdcKey, json); + } + + return matches; + } +} diff --git a/feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.common.endpoints.features.NetLoggerFeatureApi b/feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.common.endpoints.features.NetLoggerFeatureApi new file mode 100755 index 00000000..f2fdb402 --- /dev/null +++ b/feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.common.endpoints.features.NetLoggerFeatureApi @@ -0,0 +1 @@ +org.onap.policy.drools.mdc.filters.MdcFilterFeature diff --git a/feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureAPI b/feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureAPI new file mode 100755 index 00000000..f2fdb402 --- /dev/null +++ b/feature-mdc-filters/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureAPI @@ -0,0 +1 @@ +org.onap.policy.drools.mdc.filters.MdcFilterFeature -- cgit 1.2.3-korg