2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2021 Nordix Foundation.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.onap.policy.pap.main.comm;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
32 import org.onap.policy.models.base.PfModelException;
33 import org.onap.policy.models.pap.concepts.PolicyNotification;
34 import org.onap.policy.models.pdp.concepts.Pdp;
35 import org.onap.policy.models.pdp.concepts.PdpGroup;
36 import org.onap.policy.models.pdp.concepts.PdpGroupFilter;
37 import org.onap.policy.models.pdp.concepts.PdpMessage;
38 import org.onap.policy.models.pdp.concepts.PdpStateChange;
39 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
40 import org.onap.policy.models.pdp.concepts.PdpUpdate;
41 import org.onap.policy.models.pdp.enums.PdpState;
42 import org.onap.policy.models.provider.PolicyModelsProvider;
43 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
44 import org.onap.policy.pap.main.PolicyModelsProviderFactoryWrapper;
45 import org.onap.policy.pap.main.comm.msgdata.Request;
46 import org.onap.policy.pap.main.comm.msgdata.RequestListener;
47 import org.onap.policy.pap.main.comm.msgdata.StateChangeReq;
48 import org.onap.policy.pap.main.comm.msgdata.UpdateReq;
49 import org.onap.policy.pap.main.notification.DeploymentStatus;
50 import org.onap.policy.pap.main.notification.PolicyNotifier;
51 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
52 import org.onap.policy.pap.main.parameters.RequestParams;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 * Maps a PDP name to requests that modify PDPs.
59 public class PdpModifyRequestMap {
60 private static final Logger logger = LoggerFactory.getLogger(PdpModifyRequestMap.class);
62 private static final String UNEXPECTED_BROADCAST = "unexpected broadcast message: ";
65 * Maps a PDP name to its outstanding requests.
67 private final Map<String, PdpRequests> pdp2requests = new HashMap<>();
70 * PDP modification lock.
72 private final Object modifyLock;
75 * The configuration parameters.
77 private final PdpModifyRequestMapParams params;
80 * Factory for PAP DAO.
82 private final PolicyModelsProviderFactoryWrapper daoFactory;
85 * Used to notify when policy updates completes.
87 private final PolicyNotifier policyNotifier;
90 * Used to undeploy policies from the system, when they cannot be deployed to a PDP.
93 * Note: there's a "catch-22" here. The request map needs an undeployer, but the
94 * undeployer needs the request map. Consequently, the request map is created first,
95 * then the undeployer, and finally, this field is set.
98 private PolicyUndeployer policyUndeployer;
102 * Constructs the object.
104 * @param params configuration parameters
106 * @throws IllegalArgumentException if a required parameter is not set
108 public PdpModifyRequestMap(PdpModifyRequestMapParams params) {
111 this.params = params;
112 this.modifyLock = params.getModifyLock();
113 this.daoFactory = params.getDaoFactory();
114 this.policyNotifier = params.getPolicyNotifier();
118 * Determines if the map contains any requests.
120 * @return {@code true} if the map is empty, {@code false} otherwise
122 public boolean isEmpty() {
123 return pdp2requests.isEmpty();
127 * Stops publishing requests to the given PDP.
129 * @param pdpName PDP name
131 public void stopPublishing(String pdpName) {
132 synchronized (modifyLock) {
133 PdpRequests requests = pdp2requests.remove(pdpName);
134 if (requests != null) {
135 requests.stopPublishing();
141 * Adds a pair of requests to the map.
143 * @param update the UPDATE request or {@code null}
144 * @param stateChange the STATE-CHANGE request or {@code null}
146 public void addRequest(PdpUpdate update, PdpStateChange stateChange) {
147 if (update == null) {
148 addRequest(stateChange);
150 } else if (stateChange == null) {
153 } else if (stateChange.getState() == PdpState.ACTIVE) {
154 // publish update before activating
155 synchronized (modifyLock) {
157 addRequest(stateChange);
161 // deactivate before publishing update
162 synchronized (modifyLock) {
163 addRequest(stateChange);
170 * Adds an UPDATE request to the map.
172 * @param update the UPDATE request or {@code null}
174 public void addRequest(PdpUpdate update) {
175 if (update == null) {
179 if (isBroadcast(update)) {
180 throw new IllegalArgumentException(UNEXPECTED_BROADCAST + update);
184 RequestParams reqparams = new RequestParams()
185 .setMaxRetryCount(params.getParams().getUpdateParameters().getMaxRetryCount())
186 .setTimers(params.getUpdateTimers())
187 .setModifyLock(params.getModifyLock())
188 .setPdpPublisher(params.getPdpPublisher())
189 .setResponseDispatcher(params.getResponseDispatcher());
192 String name = update.getName() + " " + PdpUpdate.class.getSimpleName();
193 UpdateReq request = new UpdateReq(reqparams, name, update);
195 addSingleton(request);
199 * Adds a STATE-CHANGE request to the map.
201 * @param stateChange the STATE-CHANGE request or {@code null}
203 public void addRequest(PdpStateChange stateChange) {
204 if (stateChange == null) {
208 if (isBroadcast(stateChange)) {
209 throw new IllegalArgumentException(UNEXPECTED_BROADCAST + stateChange);
213 RequestParams reqparams = new RequestParams()
214 .setMaxRetryCount(params.getParams().getStateChangeParameters().getMaxRetryCount())
215 .setTimers(params.getStateChangeTimers())
216 .setModifyLock(params.getModifyLock())
217 .setPdpPublisher(params.getPdpPublisher())
218 .setResponseDispatcher(params.getResponseDispatcher());
221 String name = stateChange.getName() + " " + PdpStateChange.class.getSimpleName();
222 StateChangeReq request = new StateChangeReq(reqparams, name, stateChange);
224 addSingleton(request);
228 * Determines if a message is a broadcast message.
230 * @param message the message to examine
231 * @return {@code true} if the message is a broadcast message, {@code false} if
232 * destined for a single PDP
234 private boolean isBroadcast(PdpMessage message) {
235 return (message.getName() == null);
239 * Configures and adds a request to the map.
241 * @param request the request to be added
243 private void addSingleton(Request request) {
245 synchronized (modifyLock) {
246 PdpRequests requests = pdp2requests.computeIfAbsent(request.getMessage().getName(), this::makePdpRequests);
248 request.setListener(new SingletonListener(requests, request));
249 requests.addSingleton(request);
254 * Removes a PDP from all active groups.
256 * @param pdpName name of the PDP to be removed
257 * @return {@code true} if the PDP was removed from a group, {@code false} if it was
258 * not assigned to a group
259 * @throws PfModelException if an error occurred
261 public boolean removeFromGroups(String pdpName) throws PfModelException {
263 try (PolicyModelsProvider dao = daoFactory.create()) {
265 PdpGroupFilter filter = PdpGroupFilter.builder().groupState(PdpState.ACTIVE).build();
266 List<PdpGroup> groups = dao.getFilteredPdpGroups(filter);
267 List<PdpGroup> updates = new ArrayList<>(1);
269 DeploymentStatus status = new DeploymentStatus(dao);
271 for (PdpGroup group : groups) {
272 if (removeFromGroup(pdpName, group)) {
274 status.loadByGroup(group.getName());
275 status.deleteDeployment(pdpName);
279 if (updates.isEmpty()) {
283 dao.updatePdpGroups(updates);
285 PolicyNotification notification = new PolicyNotification();
286 status.flush(notification);
288 policyNotifier.publish(notification);
296 * Removes a PDP from a group.
298 * @param pdpName name of the PDP to be removed
299 * @param group group from which it should be removed
300 * @return {@code true} if the PDP was removed from the group, {@code false} if it was
301 * not assigned to the group
303 private boolean removeFromGroup(String pdpName, PdpGroup group) {
304 for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
305 if (removeFromSubgroup(pdpName, group, subgrp)) {
314 * Removes a PDP from a subgroup.
316 * @param pdpName name of the PDP to be removed
317 * @param group group from which to attempt to remove the PDP
318 * @param subgrp subgroup from which to attempt to remove the PDP
319 * @return {@code true} if the PDP was removed, {@code false} if the PDP was not in
322 private boolean removeFromSubgroup(String pdpName, PdpGroup group, PdpSubGroup subgrp) {
324 Iterator<Pdp> iter = subgrp.getPdpInstances().iterator();
326 while (iter.hasNext()) {
327 Pdp instance = iter.next();
329 if (pdpName.equals(instance.getInstanceId())) {
330 logger.info("removed {} from group={} subgroup={}", pdpName, group.getName(), subgrp.getPdpType());
332 subgrp.setCurrentInstanceCount(subgrp.getPdpInstances().size());
341 * Creates a new set of requests for a PDP. May be overridden by junit tests.
343 * @param pdpName PDP name
344 * @return a new set of requests
346 protected PdpRequests makePdpRequests(String pdpName) {
347 return new PdpRequests(pdpName, policyNotifier);
351 * Listener for singleton request events.
353 private class SingletonListener implements RequestListener {
354 private final PdpRequests requests;
355 private final Request request;
356 private final String pdpName;
358 public SingletonListener(PdpRequests requests, Request request) {
359 this.requests = requests;
360 this.request = request;
361 this.pdpName = requests.getPdpName();
365 public void failure(String responsePdpName, String reason) {
366 Collection<ToscaConceptIdentifier> undeployPolicies = requestCompleted(responsePdpName);
367 if (undeployPolicies.isEmpty()) {
368 // nothing to undeploy
373 * Undeploy the extra policies. Note: this will likely cause a new message to
374 * be assigned to the request, thus we must re-start it after making the
377 PdpMessage oldmsg = request.getMessage();
380 logger.warn("undeploy policies from {}:{} that failed to deploy: {}", oldmsg.getPdpGroup(),
381 oldmsg.getPdpSubgroup(), undeployPolicies);
382 policyUndeployer.undeploy(oldmsg.getPdpGroup(), oldmsg.getPdpSubgroup(), undeployPolicies);
383 } catch (PfModelException | RuntimeException e) {
384 logger.error("cannot undeploy policies {}", undeployPolicies, e);
387 if (request.getMessage() == oldmsg) {
388 // message is unchanged - start the next request
389 startNextRequest(request);
391 // message changed - restart the request
392 request.startPublishing();
397 public void success(String responsePdpName) {
398 requestCompleted(responsePdpName);
402 * Handles a request completion, starting the next request, if there is one.
404 * @param responsePdpName name of the PDP provided in the response
405 * @return a list of policies to be undeployed
407 private Collection<ToscaConceptIdentifier> requestCompleted(String responsePdpName) {
408 if (!pdpName.equals(responsePdpName)) {
409 return Collections.emptyList();
412 if (pdp2requests.get(pdpName) != requests) {
413 logger.info("discard old requests for {}", responsePdpName);
414 requests.stopPublishing();
415 return Collections.emptyList();
418 if (!requests.isFirstInQueue(request)) {
419 logger.error("request is not first in the queue {}", request.getMessage());
420 return Collections.emptyList();
423 Collection<ToscaConceptIdentifier> undeployPolicies = request.getUndeployPolicies();
424 if (undeployPolicies.isEmpty()) {
425 // nothing to undeploy - just start the next request
426 startNextRequest(request);
429 return undeployPolicies;
433 public void retryCountExhausted() {
438 * Starts the next request associated with a PDP.
440 * @param request the request that just completed
442 private void startNextRequest(Request request) {
443 if (!requests.startNextRequest(request)) {
444 pdp2requests.remove(pdpName, requests);
449 * Removes a PDP from its subgroup.
451 private void removePdp() {
452 requests.stopPublishing();
454 // remove the requests from the map
455 if (!pdp2requests.remove(pdpName, requests)) {
456 // wasn't in the map - the requests must be old
457 logger.warn("discarding old requests for {}", pdpName);
461 logger.warn("removing {}", pdpName);
463 // remove the PDP from all groups
465 removeFromGroups(pdpName);
466 } catch (PfModelException e) {
467 logger.info("unable to remove PDP {} from subgroup", pdpName, e);