added some comments for better comprehension
[holmes/rule-management.git] / rulemgt / src / main / java / org / onap / holmes / rulemgt / RuleAllocator.java
1 /**
2  * Copyright 2017-2021 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;
18
19 import lombok.extern.slf4j.Slf4j;
20 import org.jvnet.hk2.annotations.Service;
21 import org.onap.holmes.common.api.entity.CorrelationRule;
22 import org.onap.holmes.common.exception.CorrelationException;
23 import org.onap.holmes.common.utils.DbDaoUtil;
24 import org.onap.holmes.rulemgt.bolt.enginebolt.EngineWrapper;
25 import org.onap.holmes.rulemgt.db.CorrelationRuleDao;
26 import org.onap.holmes.rulemgt.tools.EngineTools;
27 import org.onap.holmes.rulemgt.wrapper.RuleMgtWrapper;
28 import org.onap.holmes.rulemgt.wrapper.RuleQueryWrapper;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import javax.annotation.PostConstruct;
33 import javax.inject.Inject;
34 import java.util.*;
35 import java.util.concurrent.TimeUnit;
36
37 import static java.util.concurrent.TimeUnit.SECONDS;
38
39 @Slf4j
40 @Service
41 public class RuleAllocator {
42     private static final Logger LOGGER = LoggerFactory.getLogger(RuleAllocator.class);
43
44     public final static int ENABLE = 1;
45     public final static int RETRY_TIMES = 5;
46     public final static long RETRY_INTERVAL_SEC = 15;
47     private RuleMgtWrapper ruleMgtWrapper;
48     private RuleQueryWrapper ruleQueryWrapper;
49     private EngineWrapper engineWrapper;
50     private EngineTools engineTools;
51     private CorrelationRuleDao correlationRuleDao;
52
53     @Inject
54     public RuleAllocator(RuleMgtWrapper ruleMgtWrapper, RuleQueryWrapper ruleQueryWrapper,
55                          EngineWrapper engineWrapper, EngineTools engineTools, DbDaoUtil daoUtil) {
56         this.ruleMgtWrapper = ruleMgtWrapper;
57         this.ruleQueryWrapper = ruleQueryWrapper;
58         this.engineWrapper = engineWrapper;
59         this.engineTools = engineTools;
60         correlationRuleDao = daoUtil.getJdbiDaoByOnDemand(CorrelationRuleDao.class);
61     }
62
63     @PostConstruct
64     private void initialize() {
65         new Timer("RuleAllocatorTimer").schedule(new TimerTask() {
66
67             public void run() {
68                 try {
69                     allocateRules();
70                 } catch (Exception e) {
71                     LOGGER.error("Failed to reallocate rules.", e);
72                 }
73             }
74
75         }, SECONDS.toMillis(10), SECONDS.toMillis(30));
76     }
77
78     public synchronized void allocateRules() throws Exception {
79         List<String> engines = engineTools.getInstanceList();
80
81         if (engines == null) {
82             return;
83         }
84
85         int numOfEngines = engines.size();
86         LOGGER.info(String.format("There are %d engine instance(s) running currently.", numOfEngines));
87
88         List<String> legacyEngineInstances = engineTools.getLegacyEngineInstances();
89         if (legacyEngineInstances == null) {
90             return;
91         }
92
93         if (legacyEngineInstances.size() < numOfEngines) { // extend
94             List<CorrelationRule> rules2Allocate = calculateRule(legacyEngineInstances, numOfEngines);
95             List<CorrelationRule> rules2Delete = copyList(rules2Allocate);
96             List<String> newInstanceIds = sortOutNewEngineInstances(engines, legacyEngineInstances);
97             distributeRules(newInstanceIds, rules2Allocate);
98             cleanUpRulesFromEngines(rules2Delete, legacyEngineInstances);
99         } else { // destroy
100             // If new engine instances share the same IP addresses with the old ones, the
101             // rule management module will simply leave the them to cope with the legacy rules.
102             // Here, it only takes care of the rules that need to be moved from one IP address to another.
103             List<String> destroyed = getDestroyedEngines(engines, legacyEngineInstances);
104             distributeRules(getRemainingEngines(engines, destroyed), getRules(destroyed));
105         }
106     }
107
108     private List<CorrelationRule> copyList(List<CorrelationRule> rules) {
109         List<CorrelationRule> ret = new ArrayList<>(rules.size());
110         for (CorrelationRule r : rules) {
111             ret.add((CorrelationRule) r.clone());
112         }
113         return ret;
114     }
115
116     // When the engine is expanding, the rules that need to be allocated are calculated.
117     private List<CorrelationRule> calculateRule(List<String> existingEngineIps,
118                                                 int latestEngineInsNum) throws CorrelationException {
119         List<CorrelationRule> enabledRules = ruleQueryWrapper.queryRuleByEnable(ENABLE);
120         int ruleCount = 0;
121         if (enabledRules != null) {
122             ruleCount = enabledRules.size();
123         }
124         // Average number of rule that's to be allocate into each instance
125         int count = ruleCount / latestEngineInsNum;
126         // The number of remaining rules (to be allocated) after each instance has been allocated with the average number of rules.
127         int remainder = ruleCount % latestEngineInsNum;
128
129         List<CorrelationRule> ret = new ArrayList<>();
130         for (String ip : existingEngineIps) {
131             List<CorrelationRule> rules = ruleQueryWrapper.queryRuleByEngineInstance(ip);
132             List<CorrelationRule> tmp = rules.subList(count + (remainder-- / existingEngineIps.size()), rules.size());
133             ret.addAll(tmp);
134         }
135         return ret;
136     }
137
138     // Rules that need to be allocated after the engine is destroyed
139     private List<CorrelationRule> getRules(List<String> destroyIpList) throws CorrelationException {
140         List<CorrelationRule> rules = new ArrayList<>();
141         try {
142             if (destroyIpList != null) {
143                 for (String ip : destroyIpList) {
144                     rules.addAll(ruleQueryWrapper.queryRuleByEngineInstance(ip));
145                 }
146             }
147         } catch (CorrelationException e) {
148             LOGGER.error("method getRules get data from DB failed !", e);
149         }
150         return rules;
151     }
152
153     // Extended IP
154     private List<String> sortOutNewEngineInstances(List<String> newIps, List<String> oldIps) {
155         List<String> ret = new ArrayList<>();
156
157         for (String ip : newIps) {
158             if (!oldIps.contains(ip)) {
159                 ret.add(ip);
160             }
161         }
162         return ret;
163     }
164
165     // Destroyed IP
166     private List<String> getDestroyedEngines(List<String> latest, List<String> existing) {
167         List<String> ret = new ArrayList<>();
168         for (String ip : existing) {
169             if (!latest.contains(ip)) {
170                 ret.add(ip);
171             }
172         }
173         return ret;
174     }
175
176     // Residual IP after destruction
177     private List<String> getRemainingEngines(List<String> all, List<String> destroyed) {
178         List<String> ret = new ArrayList<>();
179         for (String ip : all) {
180             if (!destroyed.contains(ip)) {
181                 ret.add(ip);
182             }
183         }
184         return ret;
185     }
186
187     private void distributeRules(List<String> instanceIps, List<CorrelationRule> rules) throws CorrelationException {
188         List<String> sortedIps = sortIpByRuleNumDesc(instanceIps);
189
190         for (int i = 0, j = 0; j < rules.size(); i++, j++) {
191             int index = i % sortedIps.size();
192             String ip = sortedIps.get(index);
193             CorrelationRule rule = rules.get(j);
194             rule.setEngineInstance(ip);
195             allocateRule(rule, ip);
196         }
197     }
198
199     // Sorted by the number of rules each engine contains, in a descending order.
200     private List<String> sortIpByRuleNumDesc(List<String> ips) {
201         List<CorrelationRule> rules;
202         Map<String, Integer> ruleNumOfEngines = new HashMap();
203
204         try {
205             for (String ip : ips) {
206                 rules = ruleQueryWrapper.queryRuleByEngineInstance(ip);
207                 if (rules != null) {
208                     ruleNumOfEngines.put(ip, rules.size());
209                 }
210             }
211         } catch (Exception e) {
212             LOGGER.error("getEngineWithLeastRules failed !", e);
213         }
214
215         List<Map.Entry<String, Integer>> sortedEntries = new ArrayList<>(ruleNumOfEngines.entrySet());
216         Collections.sort(sortedEntries, (o1, o2) -> o2.getValue() - o1.getValue());
217
218         List<String> ret = new ArrayList<>();
219         for (Map.Entry<String, Integer> entry : sortedEntries) {
220             ret.add(entry.getKey());
221         }
222         return ret;
223     }
224
225     private void allocateRule(CorrelationRule rule, String ip) throws CorrelationException {
226         // Retry for a couple of times in case of deployment failure
227         // due to unfinished initialization procedures of engine instances.
228         for (int i = 0; i <= RETRY_TIMES; ++i) {
229             try {
230                 ruleMgtWrapper.deployRule2Engine(rule, ip);
231                 correlationRuleDao.updateRule(rule);
232                 // If the codes reach here, it means everything's okay. There's no need to run the loop more.
233                 break;
234             } catch (CorrelationException e) {
235                 LOGGER.warn(String.format("Failed to allocate rule <%s> to <%s>. Retry: %d.",
236                         rule.getName(), ip, i), e);
237                 if (i == RETRY_TIMES) {
238                     throw new CorrelationException(String.format("Failed to allocate rule <%s> to <%s>",
239                             rule.getName(), ip), e);
240                 }
241                 try {
242                     SECONDS.sleep(RETRY_INTERVAL_SEC * (i + 1));
243                 } catch (InterruptedException interruptedException) {
244                     LOGGER.info(interruptedException.getMessage(), interruptedException);
245                 }
246             }
247         }
248     }
249
250     private void cleanUpRulesFromEngines(List<CorrelationRule> rules, List<String> ipList) {
251         try {
252             for (String ip : ipList) {
253                 for (CorrelationRule rule : rules) {
254                     if (ip.equals(rule.getEngineInstance())) {
255                         engineWrapper.deleteRuleFromEngine(rule.getPackageName(), ip);
256                     }
257                 }
258             }
259         } catch (CorrelationException e) {
260             LOGGER.error("When the engine is extended, deleting rule failed", e);
261         }
262     }
263 }