Changed the name of a constant
[holmes/rule-management.git] / rulemgt / src / main / java / org / onap / holmes / rulemgt / dcae / ConfigFileScanningTask.java
1 /**
2  * Copyright 2021-2022 ZTE Corporation.
3  * <p>
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * <p>
8  * http://www.apache.org/licenses/LICENSE-2.0
9  * <p>
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.onap.holmes.rulemgt.dcae;
18
19 import com.google.gson.JsonArray;
20 import com.google.gson.JsonElement;
21 import com.google.gson.JsonObject;
22 import com.google.gson.JsonParser;
23 import org.apache.commons.lang3.StringUtils;
24 import org.onap.holmes.common.ConfigFileScanner;
25 import org.onap.holmes.common.utils.CommonUtils;
26 import org.onap.holmes.common.utils.FileUtils;
27 import org.onap.holmes.common.utils.JerseyClient;
28 import org.onap.holmes.rulemgt.bean.request.RuleCreateRequest;
29 import org.onap.holmes.rulemgt.bean.response.RuleQueryListResponse;
30 import org.onap.holmes.rulemgt.bean.response.RuleResult4API;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import jakarta.ws.rs.client.Entity;
35 import jakarta.ws.rs.core.MediaType;
36 import java.io.File;
37 import java.nio.file.Paths;
38 import java.util.*;
39 import java.util.stream.Collectors;
40
41 public class ConfigFileScanningTask implements Runnable {
42     final public static long POLLING_PERIOD = 30L;
43     final private static Logger LOGGER = LoggerFactory.getLogger(ConfigFileScanningTask.class);
44     final private static long FILE_SIZE_LMT = 1024 * 1024 * 10; // 10MB
45     final private static String DEFAULT_CREATOR = "__SYSTEM__DEFAULT__";
46     private String configFile = "/opt/hrmrules/index.json";
47     private ConfigFileScanner configFileScanner;
48     private String url;
49
50     public ConfigFileScanningTask(ConfigFileScanner configFileScanner) {
51         this.configFileScanner = configFileScanner;
52         this.url = getRequestPref() + "://127.0.0.1:9101/api/holmes-rule-mgmt/v1/rule";
53     }
54
55     @Override
56     public void run() {
57         List<RuleResult4API> deployedRules = null;
58         boolean isRuleQueryAvailable = true;
59
60         try {
61             deployedRules = getExistingRules();
62         } catch (Exception e) {
63             LOGGER.warn("Failed to get existing rules for comparison.", e);
64             isRuleQueryAvailable = false;
65         }
66
67         // If it fails to load rule through API, it means that something must be wrong with the
68         // holmes-rule-mgmt service. Hence, there's no need to go on with remaining steps.
69         if (!isRuleQueryAvailable) {
70             return;
71         }
72
73         // Contents for configInEffect are <closedControlLoop>:<ruleContents> pairs.
74         Map<String, String> configInEffect = new HashMap();
75         for (RuleResult4API ruleResult4API : deployedRules) {
76             configInEffect.put(ruleResult4API.getLoopControlName(), ruleResult4API.getContent());
77         }
78
79         if (null == configFileScanner) {
80             configFileScanner = new ConfigFileScanner();
81         }
82
83         try {
84             Map<String, String> newConfig = extractConfigItems(configFileScanner.scan(configFile));
85
86             // deal with newly added rules
87             final Set<String> existingKeys = new HashSet(configInEffect.keySet());
88             final Set<String> newKeys = new HashSet(newConfig.keySet());
89             newKeys.stream()
90                     .filter(key -> !existingKeys.contains(key))
91                     .forEach(key -> {
92                         if (deployRule(key, newConfig.get(key))) {
93                             LOGGER.info("Rule '{}' has been deployed.", key);
94                         }
95                     });
96
97             // deal with removed rules
98             final List<RuleResult4API> existingRules = deployedRules;
99             existingKeys.stream().filter(key -> !newKeys.contains(key)).forEach(key -> {
100                 if (deleteRule(find(existingRules, key))) {
101                     LOGGER.info("Rule '{}' has been removed.", key);
102                 }
103             });
104
105             // deal with changed rules
106             existingKeys.stream().filter(key -> newKeys.contains(key)).forEach(key -> {
107                 if (changed(configInEffect.get(key), newConfig.get(key))) {
108                     if (deleteRule(find(existingRules, key))) {
109                         deployRule(key, newConfig.get(key));
110                         LOGGER.info("Rule '{}' has been updated.", key);
111                     }
112                 }
113             });
114         } catch (Exception e) {
115             LOGGER.warn("Unhandled error: \n" + e.getMessage(), e);
116         }
117     }
118
119     private Map<String, String> extractConfigItems(Map<String, String> configFiles) {
120         Map<String, String> ret = new HashMap();
121         for (Map.Entry entry : configFiles.entrySet()) {
122             JsonArray ja = JsonParser.parseString(entry.getValue().toString()).getAsJsonArray();
123             Iterator<JsonElement> iterator = ja.iterator();
124             while (iterator.hasNext()) {
125                 JsonObject jo = iterator.next().getAsJsonObject();
126                 String contents = readFile(jo.get("file").getAsString());
127                 if (StringUtils.isNotBlank(contents)) {
128                     ret.put(jo.get("closedControlLoopName").getAsString(), contents);
129                 }
130             }
131         }
132         return ret;
133     }
134
135     private String normalizePath(String path) {
136         if (!path.startsWith("/")) {
137             return Paths.get(new File(configFile).getParent(), path).toString();
138         }
139         return path;
140     }
141
142     private String readFile(String path) {
143         String finalPath = normalizePath(path);
144         File file = new File(finalPath);
145         if (file.exists() && !file.isDirectory() && file.length() <= FILE_SIZE_LMT) {
146             return FileUtils.readTextFile(finalPath);
147         } else {
148             LOGGER.warn("The file {} does not exist or it is a directory or it is too large to load.", finalPath);
149         }
150         return null;
151     }
152
153     private RuleResult4API find(final List<RuleResult4API> rules, String clName) {
154         for (RuleResult4API rule : rules) {
155             if (rule.getLoopControlName().equals(clName)) {
156                 return rule;
157             }
158         }
159         return null;
160     }
161
162     private boolean changed(String con1, String con2) {
163         // if either of the arguments is null, consider it as invalid and unchanged
164         if (con1 == null || con2 == null) {
165             return false;
166         }
167
168         if (!con1.replaceAll("\\s", StringUtils.EMPTY)
169                 .equals(con2.replaceAll("\\s", StringUtils.EMPTY))) {
170             return true;
171         }
172
173         return false;
174     }
175
176     private List<RuleResult4API> getExistingRules() {
177         RuleQueryListResponse ruleQueryListResponse = JerseyClient.newInstance().get(url, RuleQueryListResponse.class);
178         List<RuleResult4API> deployedRules = Collections.EMPTY_LIST;
179         if (null != ruleQueryListResponse) {
180             deployedRules = ruleQueryListResponse.getCorrelationRules()
181                     .stream().filter(r -> DEFAULT_CREATOR.equals(r.getCreator())).collect(Collectors.toList());
182         }
183         return deployedRules;
184     }
185
186     private boolean deployRule(String clName, String contents) {
187         RuleCreateRequest ruleCreateRequest = getRuleCreateRequest(clName, contents);
188         if (JerseyClient.newInstance().header("Accept", MediaType.APPLICATION_JSON)
189                 .put(url, Entity.json(ruleCreateRequest)) == null) {
190             LOGGER.error("Failed to deploy rule: {}.", clName);
191             return false;
192         }
193         return true;
194     }
195
196     private RuleCreateRequest getRuleCreateRequest(String clName, String contents) {
197         RuleCreateRequest ruleCreateRequest = new RuleCreateRequest();
198         ruleCreateRequest.setLoopControlName(clName);
199         ruleCreateRequest.setRuleName(clName);
200         ruleCreateRequest.setContent(contents);
201         ruleCreateRequest.setDescription("");
202         ruleCreateRequest.setEnabled(1);
203         ruleCreateRequest.setCreator(DEFAULT_CREATOR);
204         return ruleCreateRequest;
205     }
206
207     private boolean deleteRule(RuleResult4API rule) {
208         if (rule == null) {
209             LOGGER.info("No rule found, nothing to delete.");
210             return false;
211         }
212         if (null == JerseyClient.newInstance().delete(url + "/" + rule.getRuleId())) {
213             LOGGER.warn("Failed to delete rule, the rule id is: {}", rule.getRuleId());
214             return false;
215         }
216         return true;
217     }
218
219     private String getRequestPref() {
220         return CommonUtils.isHttpsEnabled() ? JerseyClient.PROTOCOL_HTTPS : JerseyClient.PROTOCOL_HTTP;
221     }
222 }