/**
* Copyright 2021 ZTE Corporation.
*
* 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.
*/
package org.onap.holmes.rulemgt.dcae;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang.StringUtils;
import org.onap.holmes.common.ConfigFileScanner;
import org.onap.holmes.common.utils.CommonUtils;
import org.onap.holmes.common.utils.FileUtils;
import org.onap.holmes.common.utils.JerseyClient;
import org.onap.holmes.rulemgt.bean.request.RuleCreateRequest;
import org.onap.holmes.rulemgt.bean.response.RuleQueryListResponse;
import org.onap.holmes.rulemgt.bean.response.RuleResult4API;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import java.io.File;
import java.nio.file.Paths;
import java.util.*;
public class ConfigFileScanningTask implements Runnable {
final public static long POLLING_PERIOD = 30L;
final private static Logger LOGGER = LoggerFactory.getLogger(ConfigFileScanningTask.class);
final private static long FILE_SIZE_LMT = 1024 * 1024 * 10; // 10MB
final private Map configInEffect = new HashMap(); // Contents for configInEffect are : pairs.
private String configFile = "/opt/hrmrules/index.json";
private ConfigFileScanner configFileScanner;
private String url;
public ConfigFileScanningTask(ConfigFileScanner configFileScanner) {
this.configFileScanner = configFileScanner;
this.url = getRequestPref() + "://127.0.0.1:9101/api/holmes-rule-mgmt/v1/rule";
}
@Override
public void run() {
List deployedRules = null;
boolean isRuleQueryAvailable = true;
try {
deployedRules = getExistingRules();
} catch (Exception e) {
LOGGER.warn("Failed to get existing rules for comparison.", e);
isRuleQueryAvailable = false;
}
// If it fails to load rule through API, it means that something must be wrong with the
// holmes-rule-mgmt service. Hence, there's no need to go on with remaining steps.
if (!isRuleQueryAvailable) {
return;
}
for (RuleResult4API ruleResult4API : deployedRules) {
configInEffect.put(ruleResult4API.getLoopControlName(), ruleResult4API.getContent());
}
if (null == configFileScanner) {
configFileScanner = new ConfigFileScanner();
}
try {
Map newConfig = extractConfigItems(configFileScanner.scan(configFile));
// deal with newly added rules
final Set existingKeys = new HashSet(configInEffect.keySet());
final Set newKeys = new HashSet(newConfig.keySet());
newKeys.stream()
.filter(key -> !existingKeys.contains(key))
.forEach(key -> {
if (deployRule(key, newConfig.get(key))) {
LOGGER.info("Rule '{}' has been deployed.", key);
}
});
// deal with removed rules
final List existingRules = deployedRules;
existingKeys.stream().filter(key -> !newKeys.contains(key)).forEach(key -> {
if (deleteRule(find(existingRules, key))) {
LOGGER.info("Rule '{}' has been removed.", key);
}
});
// deal with changed rules
existingKeys.stream().filter(key -> newKeys.contains(key)).forEach(key -> {
if (changed(configInEffect.get(key), newConfig.get(key))) {
if (deleteRule(find(existingRules, key))) {
deployRule(key, newConfig.get(key));
LOGGER.info("Rule '{}' has been updated.", key);
}
}
});
} catch (Exception e) {
LOGGER.warn("Unhandled error: \n" + e.getMessage(), e);
}
}
private Map extractConfigItems(Map configFiles) {
Map ret = new HashMap();
for (Map.Entry entry : configFiles.entrySet()) {
JsonArray ja = JsonParser.parseString(entry.getValue().toString()).getAsJsonArray();
Iterator iterator = ja.iterator();
while (iterator.hasNext()) {
JsonObject jo = iterator.next().getAsJsonObject();
String contents = readFile(jo.get("file").getAsString());
if (StringUtils.isNotBlank(contents)) {
ret.put(jo.get("closedControlLoopName").getAsString(), contents);
}
}
}
return ret;
}
private String normalizePath(String path) {
if (!path.startsWith("/")) {
return Paths.get(new File(configFile).getParent(), path).toString();
}
return path;
}
private String readFile(String path) {
String finalPath = normalizePath(path);
File file = new File(finalPath);
if (file.exists() && !file.isDirectory() && file.length() <= FILE_SIZE_LMT) {
return FileUtils.readTextFile(finalPath);
} else {
LOGGER.warn("The file {} does not exist or it is a directory or it is too large to load.", finalPath);
}
return null;
}
private RuleResult4API find(final List rules, String clName) {
for (RuleResult4API rule : rules) {
if (rule.getLoopControlName().equals(clName)) {
return rule;
}
}
return null;
}
private boolean changed(String con1, String con2) {
// if either of the arguments is null, consider it as invalid and unchanged
if (con1 == null || con2 == null) {
return false;
}
if (!con1.replaceAll("\\s", StringUtils.EMPTY)
.equals(con2.replaceAll("\\s", StringUtils.EMPTY))) {
return true;
}
return false;
}
private List getExistingRules() {
RuleQueryListResponse ruleQueryListResponse = JerseyClient.newInstance().get(url, RuleQueryListResponse.class);
List deployedRules = Collections.EMPTY_LIST;
if (null != ruleQueryListResponse) {
deployedRules = ruleQueryListResponse.getCorrelationRules();
}
return deployedRules;
}
private boolean deployRule(String clName, String contents) {
RuleCreateRequest ruleCreateRequest = getRuleCreateRequest(clName, contents);
if (JerseyClient.newInstance().header("Accept", MediaType.APPLICATION_JSON)
.put(url, Entity.json(ruleCreateRequest)) == null) {
LOGGER.error("Failed to deploy rule: {}.", clName);
return false;
}
return true;
}
private RuleCreateRequest getRuleCreateRequest(String clName, String contents) {
RuleCreateRequest ruleCreateRequest = new RuleCreateRequest();
ruleCreateRequest.setLoopControlName(clName);
ruleCreateRequest.setRuleName(clName);
ruleCreateRequest.setContent(contents);
ruleCreateRequest.setDescription("");
ruleCreateRequest.setEnabled(1);
return ruleCreateRequest;
}
private boolean deleteRule(RuleResult4API rule) {
if (rule == null) {
LOGGER.info("No rule found, nothing to delete.");
return false;
}
if (null == JerseyClient.newInstance().delete(url + "/" + rule.getRuleId())) {
LOGGER.warn("Failed to delete rule, the rule id is: {}", rule.getRuleId());
return false;
}
return true;
}
private String getRequestPref() {
return CommonUtils.isHttpsEnabled() ? JerseyClient.PROTOCOL_HTTPS : JerseyClient.PROTOCOL_HTTP;
}
}