[SLICEMS] Add bw decrease logics and enhance runtime config feature
[dcaegen2/services.git] / components / slice-analysis-ms / src / main / java / org / onap / slice / analysis / ms / service / ccvpn / FixedUpperBoundStrategy.java
1 /*******************************************************************************
2  *  ============LICENSE_START=======================================================
3  *  slice-analysis-ms
4  *  ================================================================================
5  *   Copyright (C) 2022 Huawei Canada Limited.
6  *   Copyright (C) 2022 Huawei Technologies Co., Ltd.
7  *  ==============================================================================
8  *     Licensed under the Apache License, Version 2.0 (the "License");
9  *     you may not use this file except in compliance with the License.
10  *     You may obtain a copy of the License at
11  *
12  *          http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *     Unless required by applicable law or agreed to in writing, software
15  *     distributed under the License is distributed on an "AS IS" BASIS,
16  *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  *     See the License for the specific language governing permissions and
18  *     limitations under the License.
19  *     ============LICENSE_END=========================================================
20  *
21  *******************************************************************************/
22 package org.onap.slice.analysis.ms.service.ccvpn;
23
24 import com.google.gson.JsonObject;
25 import org.onap.slice.analysis.ms.models.Configuration;
26 import org.onap.slice.analysis.ms.service.PolicyService;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.springframework.beans.factory.annotation.Autowired;
30 import org.springframework.stereotype.Component;
31
32 import javax.annotation.PostConstruct;
33 import java.util.Arrays;
34 import java.util.Map;
35 import java.util.TreeMap;
36 import java.util.stream.Collectors;
37
38 /**
39  * Threshold strategy can be configured via configuration
40  * If "sliceanalysisms.ccvpnEvalStrategy" is set to "FixedUpperBoundStrategy", then this class is triggered.
41  */
42 @Component
43 public class FixedUpperBoundStrategy implements EvaluationStrategy{
44     private static Logger log = LoggerFactory.getLogger(FixedUpperBoundStrategy.class);
45     private Configuration configuration;
46
47     private static final String TYPE_NAME = "FixedUpperBoundStrategy";
48     private static final String SERVICE_INSTANCE_LOCATION_ID = "service-instance-location-id";
49     private static final String BANDWIDTH_TOTAL = "bandwidth-total";
50
51     /**
52      * Percentage threshold of bandwidth adjustment.
53      */
54     private static double upperThreshold;
55
56     /**
57      * Precision of bandwidth evaluation and adjustment.
58      */
59     private static double precision; // in Mbps;
60
61     @Autowired
62     BandwidthEvaluator bandwidthEvaluator;
63
64     @Autowired
65     CCVPNPmDatastore ccvpnPmDatastore;
66
67     @Autowired
68     PolicyService policyService;
69
70     @PostConstruct
71     public void init() {
72         loadConfig();
73     }
74
75     /**
76      * Periodically ensure endpoint bw adjustment is under assurance.
77      * This method will be invoked when FixedUpperBoundStrategy is set.
78      * @param event
79      */
80     @Override
81     public void execute(Event event){
82         if (event.type() == SimpleEvent.Type.PERIODIC_CHECK && isPeriodicCheckOn()){
83             log.debug("=== Processing new periodic check request: {} ===", event.time());
84             Map<Endpointkey, CCVPNPmDatastore.EvictingQueue<Integer>> usedBwMap = ccvpnPmDatastore.getUsedBwMap();
85             Map<String, Integer> candidate = new TreeMap<>();
86             for(Map.Entry<Endpointkey, CCVPNPmDatastore.EvictingQueue<Integer>> entry: usedBwMap.entrySet()) {
87                 String serviceId = entry.getKey().getCllId();
88                 Object[] usedBws = entry.getValue().tryReadToArray();
89
90                 if (!ccvpnPmDatastore.getClosedloopStatus(serviceId)) {
91                     log.info("CCVPN Evaluator Output: service {}, closed loop bw modification is off.", serviceId);
92                     continue;
93                 }
94                 if (usedBws == null) {
95                     // No enough data for evaluating
96                     log.info("CCVPN Evaluator Output: service {}, not enough data to evaluate", serviceId);
97                     continue;
98                 }
99                 if (ccvpnPmDatastore.getProvBwOfSvc(serviceId) == 0) {
100                     // Max bandwidth not cached yet
101                     log.info("CCVPN Evaluator Output: service {}, max bandwidth not cached, wait for next round", serviceId);
102                     post(new SimpleEvent(SimpleEvent.Type.AAI_BW_REQ, serviceId));
103                     continue;
104                 }
105                 double avg = Arrays.stream(usedBws)
106                         .mapToInt(o -> (int) o)
107                         .summaryStatistics()
108                         .getAverage();
109                 int provBw = ccvpnPmDatastore.getProvBwOfSvc(serviceId);
110                 int upperBw = ccvpnPmDatastore.getUpperBoundBwOfSvc(serviceId);
111                 if (needAdjust(serviceId, avg, provBw, upperBw)) {
112                     int newBw = needAdjustTo(serviceId, avg, provBw, upperBw);
113                     if(Math.abs(newBw - provBw) >= precision){
114                         log.info("CCVPN Evaluator Output: service {}, need adjustment, putting into candidate list", serviceId);
115                         candidate.put(serviceId, newBw);
116                     }
117                 }
118             }
119             // check svc under maintenance
120             Map<String , ServiceState> svcUnderMaintenance = getServicesUnderMaintenance();
121             for (Map.Entry<String, ServiceState> entry: svcUnderMaintenance.entrySet()){
122                 candidate.putIfAbsent(entry.getKey(), 0);
123             }
124             // fetch the provisioned bandwidth info if underMaintenance; otherwise send modification request
125             for(Map.Entry<String, Integer> entry: candidate.entrySet()) {
126                 //still doing adjustment
127                 String cllId = entry.getKey();
128                 Integer newBw = entry.getValue();
129                 if(!ccvpnPmDatastore.getClosedloopStatus(cllId)) {
130                     log.info("CCVPN Evaluator Output: service {} is not under closed loop assurance", cllId);
131                     continue;
132                 }
133                 if (isServiceUnderMaintenance(cllId)) {
134                     if (newBw == 0){
135                         log.info("CCVPN Evaluator Output: service {}," +
136                             " is in maintenance state, fetching bandwidth info from AAI", cllId);
137                     } else {
138                         log.info("CCVPN Evaluator Output: candidate {}," +
139                             " need an adjustment, but skipped due to in maintenance state", cllId);
140                     }
141                     post(new SimpleEvent(SimpleEvent.Type.AAI_BW_REQ, cllId));
142                     continue;
143                 }
144                 //not in the mid of adjustment; we are free to adjust.
145                 log.info("CCVPN Evaluator Output: candidate {}," +
146                     " need an adjustment, sending request to policy", entry.getKey());
147                 ccvpnPmDatastore.updateSvcState(entry.getKey(), ServiceState.UNDER_MAINTENANCE);
148                 sendModifyRequest(entry.getKey(), newBw, RequestOwner.DCAE);
149             }
150             log.debug("=== Processing periodic check complete ===");
151         }
152         if (event.type() == SimpleEvent.Type.ONDEMAND_CHECK && isOnDemandCheckOn()) {
153             log.info("=== Processing upperbound adjustment request: {} ===", event.time());
154             JsonObject payload = (JsonObject) event.subject();
155             String serviceId = payload.get(SERVICE_INSTANCE_LOCATION_ID).getAsString();
156             int newBandwidth = payload.get(BANDWIDTH_TOTAL).getAsInt();
157             log.info("Update service {} bandwidth upperbound to {} ", serviceId, newBandwidth);
158             ccvpnPmDatastore.updateUpperBoundBw(serviceId, newBandwidth);
159             log.info("=== Processing upperbound adjustment complete ===");
160         }
161     }
162
163     @Override
164     public String getName() {
165         return TYPE_NAME;
166     }
167
168     /**
169      * Post/broadcast event to the BandwidthEvaluator
170      * @param event event object
171      */
172     private void post(Event event){
173         bandwidthEvaluator.post(event);
174     }
175
176     private void loadConfig() {
177         configuration = Configuration.getInstance();
178         upperThreshold = configuration.getCcvpnEvalUpperThreshold();
179         precision = configuration.getCcvpnEvalPrecision(); // in Mbps;
180     }
181
182     private boolean isPeriodicCheckOn() {
183         configuration = Configuration.getInstance();
184         return configuration.isCcvpnEvalPeriodicCheckOn();
185     }
186
187     private boolean isOnDemandCheckOn() {
188         configuration = Configuration.getInstance();
189         return configuration.isCcvpnEvalOnDemandCheckOn();
190     }
191
192     // send modification requestion
193     private void sendModifyRequest(String cllId, Integer newBandwidth, RequestOwner owner) {
194         log.info("Sending modification request to policy. RequestOwner: {} - Service: {} change to bw: {}",
195                 owner, cllId, newBandwidth);
196         policyService.sendOnsetMessageToPolicy(
197                 policyService.formPolicyOnsetMessageForCCVPN(cllId, newBandwidth, owner)
198         );
199     }
200     // check if an adjustment is necessary
201     private boolean needAdjust(String serivceId, double used, int provBandwidth, int upper){
202         log.info("CCVPN Service Usage Analysis: usage: {}, threshold: {}, currentProvisioned {}, upperbound {}",
203             used, upperThreshold, provBandwidth, upper);
204         return provBandwidth > upper || used > upperThreshold * provBandwidth;
205     }
206
207     // calculate new bandwidth to accomodate customer
208     private int needAdjustTo(String serivceId, double used, int cur, int upper){
209         if (cur >= upper){
210             return upper;
211         }
212         int expected = (int) (Math.ceil((used / upperThreshold) * 1.2 / precision) * precision);
213         return Math.min(expected, upper);
214     }
215     // check is service under maint
216     private boolean isServiceUnderMaintenance(String serivceId) {
217         return ccvpnPmDatastore.getStatusOfSvc(serivceId) == ServiceState.UNDER_MAINTENANCE;
218     }
219     // get a collection of service under maint
220     private Map<String, ServiceState> getServicesUnderMaintenance(){
221         return ccvpnPmDatastore.getSvcStatusMap().entrySet()
222                 .stream()
223                 .filter(e -> e.getValue() == ServiceState.UNDER_MAINTENANCE)
224                 .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
225     }
226 }