Migrate from DW to Springboot
[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
40 public class ConfigFileScanningTask implements Runnable {
41     final public static long POLLING_PERIOD = 30L;
42     final private static Logger LOGGER = LoggerFactory.getLogger(ConfigFileScanningTask.class);
43     final private static long FILE_SIZE_LMT = 1024 * 1024 * 10; // 10MB
44     private String configFile = "/opt/hrmrules/index.json";
45     private ConfigFileScanner configFileScanner;
46     private String url;
47
48     public ConfigFileScanningTask(ConfigFileScanner configFileScanner) {
49         this.configFileScanner = configFileScanner;
50         this.url = getRequestPref() + "://127.0.0.1:9101/api/holmes-rule-mgmt/v1/rule";
51     }
52
53     @Override
54     public void run() {
55         List<RuleResult4API> deployedRules = null;
56         boolean isRuleQueryAvailable = true;
57
58         try {
59             deployedRules = getExistingRules();
60         } catch (Exception e) {
61             LOGGER.warn("Failed to get existing rules for comparison.", e);
62             isRuleQueryAvailable = false;
63         }
64
65         // If it fails to load rule through API, it means that something must be wrong with the
66         // holmes-rule-mgmt service. Hence, there's no need to go on with remaining steps.
67         if (!isRuleQueryAvailable) {
68             return;
69         }
70
71         // Contents for configInEffect are <closedControlLoop>:<ruleContents> pairs.
72         Map<String, String> configInEffect = new HashMap();
73         for (RuleResult4API ruleResult4API : deployedRules) {
74             configInEffect.put(ruleResult4API.getLoopControlName(), ruleResult4API.getContent());
75         }
76
77         if (null == configFileScanner) {
78             configFileScanner = new ConfigFileScanner();
79         }
80
81         try {
82             Map<String, String> newConfig = extractConfigItems(configFileScanner.scan(configFile));
83
84             // deal with newly added rules
85             final Set<String> existingKeys = new HashSet(configInEffect.keySet());
86             final Set<String> newKeys = new HashSet(newConfig.keySet());
87             newKeys.stream()
88                     .filter(key -> !existingKeys.contains(key))
89                     .forEach(key -> {
90                         if (deployRule(key, newConfig.get(key))) {
91                             LOGGER.info("Rule '{}' has been deployed.", key);
92                         }
93                     });
94
95             // deal with removed rules
96             final List<RuleResult4API> existingRules = deployedRules;
97             existingKeys.stream().filter(key -> !newKeys.contains(key)).forEach(key -> {
98                 if (deleteRule(find(existingRules, key))) {
99                     LOGGER.info("Rule '{}' has been removed.", key);
100                 }
101             });
102
103             // deal with changed rules
104             existingKeys.stream().filter(key -> newKeys.contains(key)).forEach(key -> {
105                 if (changed(configInEffect.get(key), newConfig.get(key))) {
106                     if (deleteRule(find(existingRules, key))) {
107                         deployRule(key, newConfig.get(key));
108                         LOGGER.info("Rule '{}' has been updated.", key);
109                     }
110                 }
111             });
112         } catch (Exception e) {
113             LOGGER.warn("Unhandled error: \n" + e.getMessage(), e);
114         }
115     }
116
117     private Map<String, String> extractConfigItems(Map<String, String> configFiles) {
118         Map<String, String> ret = new HashMap();
119         for (Map.Entry entry : configFiles.entrySet()) {
120             JsonArray ja = JsonParser.parseString(entry.getValue().toString()).getAsJsonArray();
121             Iterator<JsonElement> iterator = ja.iterator();
122             while (iterator.hasNext()) {
123                 JsonObject jo = iterator.next().getAsJsonObject();
124                 String contents = readFile(jo.get("file").getAsString());
125                 if (StringUtils.isNotBlank(contents)) {
126                     ret.put(jo.get("closedControlLoopName").getAsString(), contents);
127                 }
128             }
129         }
130         return ret;
131     }
132
133     private String normalizePath(String path) {
134         if (!path.startsWith("/")) {
135             return Paths.get(new File(configFile).getParent(), path).toString();
136         }
137         return path;
138     }
139
140     private String readFile(String path) {
141         String finalPath = normalizePath(path);
142         File file = new File(finalPath);
143         if (file.exists() && !file.isDirectory() && file.length() <= FILE_SIZE_LMT) {
144             return FileUtils.readTextFile(finalPath);
145         } else {
146             LOGGER.warn("The file {} does not exist or it is a directory or it is too large to load.", finalPath);
147         }
148         return null;
149     }
150
151     private RuleResult4API find(final List<RuleResult4API> rules, String clName) {
152         for (RuleResult4API rule : rules) {
153             if (rule.getLoopControlName().equals(clName)) {
154                 return rule;
155             }
156         }
157         return null;
158     }
159
160     private boolean changed(String con1, String con2) {
161         // if either of the arguments is null, consider it as invalid and unchanged
162         if (con1 == null || con2 == null) {
163             return false;
164         }
165
166         if (!con1.replaceAll("\\s", StringUtils.EMPTY)
167                 .equals(con2.replaceAll("\\s", StringUtils.EMPTY))) {
168             return true;
169         }
170
171         return false;
172     }
173
174     private List<RuleResult4API> getExistingRules() {
175         RuleQueryListResponse ruleQueryListResponse = JerseyClient.newInstance().get(url, RuleQueryListResponse.class);
176         List<RuleResult4API> deployedRules = Collections.EMPTY_LIST;
177         if (null != ruleQueryListResponse) {
178             deployedRules = ruleQueryListResponse.getCorrelationRules();
179         }
180         return deployedRules;
181     }
182
183     private boolean deployRule(String clName, String contents) {
184         RuleCreateRequest ruleCreateRequest = getRuleCreateRequest(clName, contents);
185         if (JerseyClient.newInstance().header("Accept", MediaType.APPLICATION_JSON)
186                 .put(url, Entity.json(ruleCreateRequest)) == null) {
187             LOGGER.error("Failed to deploy rule: {}.", clName);
188             return false;
189         }
190         return true;
191     }
192
193     private RuleCreateRequest getRuleCreateRequest(String clName, String contents) {
194         RuleCreateRequest ruleCreateRequest = new RuleCreateRequest();
195         ruleCreateRequest.setLoopControlName(clName);
196         ruleCreateRequest.setRuleName(clName);
197         ruleCreateRequest.setContent(contents);
198         ruleCreateRequest.setDescription("");
199         ruleCreateRequest.setEnabled(1);
200         return ruleCreateRequest;
201     }
202
203     private boolean deleteRule(RuleResult4API rule) {
204         if (rule == null) {
205             LOGGER.info("No rule found, nothing to delete.");
206             return false;
207         }
208         if (null == JerseyClient.newInstance().delete(url + "/" + rule.getRuleId())) {
209             LOGGER.warn("Failed to delete rule, the rule id is: {}", rule.getRuleId());
210             return false;
211         }
212         return true;
213     }
214
215     private String getRequestPref() {
216         return CommonUtils.isHttpsEnabled() ? JerseyClient.PROTOCOL_HTTPS : JerseyClient.PROTOCOL_HTTP;
217     }
218 }