2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2020-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.rest;
24 import com.google.gson.annotations.SerializedName;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.LinkedHashSet;
29 import java.util.List;
32 import java.util.stream.Collectors;
33 import javax.ws.rs.core.Response.Status;
35 import org.onap.policy.common.parameters.BeanValidationResult;
36 import org.onap.policy.common.parameters.BeanValidator;
37 import org.onap.policy.common.parameters.ObjectValidationResult;
38 import org.onap.policy.common.parameters.ValidationResult;
39 import org.onap.policy.common.parameters.ValidationStatus;
40 import org.onap.policy.common.parameters.annotations.NotNull;
41 import org.onap.policy.common.parameters.annotations.Pattern;
42 import org.onap.policy.common.parameters.annotations.Valid;
43 import org.onap.policy.common.utils.coder.CoderException;
44 import org.onap.policy.common.utils.coder.StandardCoder;
45 import org.onap.policy.common.utils.services.Registry;
46 import org.onap.policy.models.base.PfKey;
47 import org.onap.policy.models.base.PfModelException;
48 import org.onap.policy.models.base.PfModelRuntimeException;
49 import org.onap.policy.models.pap.concepts.PdpDeployPolicies;
50 import org.onap.policy.models.pdp.concepts.DeploymentGroup;
51 import org.onap.policy.models.pdp.concepts.DeploymentGroups;
52 import org.onap.policy.models.pdp.concepts.DeploymentSubGroup;
53 import org.onap.policy.models.pdp.concepts.Pdp;
54 import org.onap.policy.models.pdp.concepts.PdpGroup;
55 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
56 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
57 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifierOptVersion;
58 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
63 * Provider for PAP component to deploy PDP groups. The following items must be in the
66 * <li>PDP Modification Lock</li>
67 * <li>PDP Modify Request Map</li>
68 * <li>PAP DAO Factory</li>
71 public class PdpGroupDeployProvider extends ProviderBase {
72 private static final Logger logger = LoggerFactory.getLogger(PdpGroupDeployProvider.class);
73 private static final StandardCoder coder = new StandardCoder();
75 private static final String POLICY_RESULT_NAME = "policy";
79 * Constructs the object.
81 public PdpGroupDeployProvider() {
86 * Updates policies in specific PDP groups.
88 * @param groups PDP group deployments to be updated
89 * @throws PfModelException if an error occurred
91 public void updateGroupPolicies(DeploymentGroups groups) throws PfModelException {
92 ValidationResult result = groups.validatePapRest();
93 if (!result.isValid()) {
94 String msg = result.getResult().trim();
95 throw new PfModelException(Status.BAD_REQUEST, msg);
98 process(groups, this::updateGroups);
102 * Updates policies in specific PDP groups. This is the method that does the actual work.
104 * @param data session data
105 * @param groups PDP group deployments
106 * @throws PfModelException if an error occurred
108 private void updateGroups(SessionData data, DeploymentGroups groups) throws PfModelException {
109 BeanValidationResult result = new BeanValidationResult("groups", groups);
111 for (DeploymentGroup group : groups.getGroups()) {
112 PdpGroup dbgroup = data.getGroup(group.getName());
114 if (dbgroup == null) {
115 result.addResult(new ObjectValidationResult(group.getName(), group,
116 ValidationStatus.INVALID, "unknown group"));
119 result.addResult(updateGroup(data, dbgroup, group));
123 if (!result.isValid()) {
124 throw new PfModelException(Status.BAD_REQUEST, result.getResult().trim());
129 * Updates an existing group.
131 * @param data session data
132 * @param dbgroup the group, as it appears within the DB
133 * @param group the group to be added
134 * @return the validation result
135 * @throws PfModelException if an error occurred
137 private ValidationResult updateGroup(SessionData data, PdpGroup dbgroup, DeploymentGroup group)
138 throws PfModelException {
140 BeanValidationResult result = new BeanValidationResult(group.getName(), group);
142 boolean updated = updateSubGroups(data, dbgroup, group, result);
144 if (result.isValid() && updated) {
145 data.update(dbgroup);
152 * Adds or updates subgroups within the group.
154 * @param data session data
155 * @param dbgroup the group, as it appears within the DB
156 * @param group the group to be added
157 * @param result the validation result
158 * @return {@code true} if the DB group was modified, {@code false} otherwise
159 * @throws PfModelException if an error occurred
161 private boolean updateSubGroups(SessionData data, PdpGroup dbgroup, DeploymentGroup group,
162 BeanValidationResult result) throws PfModelException {
164 // create a map of existing subgroups
165 Map<String, PdpSubGroup> type2sub = new HashMap<>();
166 dbgroup.getPdpSubgroups().forEach(subgrp -> type2sub.put(subgrp.getPdpType(), subgrp));
168 boolean updated = false;
170 for (DeploymentSubGroup subgrp : group.getDeploymentSubgroups()) {
171 PdpSubGroup dbsub = type2sub.get(subgrp.getPdpType());
172 BeanValidationResult subResult = new BeanValidationResult(subgrp.getPdpType(), subgrp);
175 subResult.addResult(new ObjectValidationResult(subgrp.getPdpType(), subgrp,
176 ValidationStatus.INVALID, "unknown subgroup"));
179 updated = updateSubGroup(data, dbgroup, dbsub, subgrp, subResult) || updated;
182 result.addResult(subResult);
189 * Updates an existing subgroup.
191 * @param data session data
192 * @param dbgroup the group, from the DB, containing the subgroup
193 * @param dbsub the subgroup, from the DB
194 * @param subgrp the subgroup to be updated, updated to fully qualified versions upon
196 * @param container container for additional validation results
197 * @return {@code true} if the subgroup content was changed, {@code false} if there
199 * @throws PfModelException if an error occurred
201 private boolean updateSubGroup(SessionData data, PdpGroup dbgroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp,
202 BeanValidationResult container) throws PfModelException {
204 // perform additional validations first
205 if (!validateSubGroup(data, dbsub, subgrp, container)) {
209 boolean updated = false;
211 switch (subgrp.getAction()) {
213 updated = addPolicies(data, dbgroup.getName(), dbsub, subgrp);
216 updated = deletePolicies(data, dbgroup.getName(), dbsub, subgrp);
220 updated = updatePolicies(data, dbgroup.getName(), dbsub, subgrp);
225 // publish any changes to the PDPs
226 makeUpdates(data, dbgroup, dbsub);
233 private boolean addPolicies(SessionData data, String pdpGroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
234 throws PfModelException {
236 Set<ToscaConceptIdentifier> policies = new LinkedHashSet<>(dbsub.getPolicies());
237 policies.addAll(subgrp.getPolicies());
239 DeploymentSubGroup subgrp2 = new DeploymentSubGroup(subgrp);
240 subgrp2.getPolicies().clear();
241 subgrp2.getPolicies().addAll(policies);
243 return updatePolicies(data, pdpGroup, dbsub, subgrp2);
246 private boolean deletePolicies(SessionData data, String pdpGroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
247 throws PfModelException {
249 Set<ToscaConceptIdentifier> policies = new LinkedHashSet<>(dbsub.getPolicies());
250 policies.removeAll(subgrp.getPolicies());
252 DeploymentSubGroup subgrp2 = new DeploymentSubGroup(subgrp);
253 subgrp2.getPolicies().clear();
254 subgrp2.getPolicies().addAll(policies);
256 return updatePolicies(data, pdpGroup, dbsub, subgrp2);
259 private boolean updatePolicies(SessionData data, String pdpGroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
260 throws PfModelException {
262 Set<ToscaConceptIdentifier> undeployed = new HashSet<>(dbsub.getPolicies());
263 undeployed.removeAll(subgrp.getPolicies());
265 Set<ToscaConceptIdentifier> deployed = new HashSet<>(subgrp.getPolicies());
266 deployed.removeAll(dbsub.getPolicies());
268 if (deployed.isEmpty() && undeployed.isEmpty()) {
269 // lists are identical
273 Set<String> pdps = dbsub.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
275 for (ToscaConceptIdentifier policyId : deployed) {
276 ToscaPolicy policyToBeDeployed = data.getPolicy(new ToscaConceptIdentifierOptVersion(policyId));
277 data.trackDeploy(policyToBeDeployed, pdps, pdpGroup, dbsub.getPdpType());
280 for (ToscaConceptIdentifier policyId : undeployed) {
281 data.trackUndeploy(policyId, pdps, pdpGroup, dbsub.getPdpType());
284 dbsub.setPolicies(new ArrayList<>(subgrp.getPolicies()));
289 * Performs additional validations of a subgroup.
291 * @param data session data
292 * @param dbsub the subgroup, from the DB
293 * @param subgrp the subgroup to be validated, updated to fully qualified versions
295 * @param container container for additional validation results
296 * @return {@code true} if the subgroup is valid, {@code false} otherwise
297 * @throws PfModelException if an error occurred
299 private boolean validateSubGroup(SessionData data, PdpSubGroup dbsub, DeploymentSubGroup subgrp,
300 BeanValidationResult container) throws PfModelException {
302 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
304 result.addResult(validatePolicies(data, dbsub, subgrp));
305 container.addResult(result);
307 return result.isValid();
311 * Performs additional validations of the policies within a subgroup.
313 * @param data session data
314 * @param dbsub subgroup from the DB, or {@code null} if this is a new subgroup
315 * @param subgrp the subgroup whose policies are to be validated, updated to fully
316 * qualified versions upon return
317 * @param result the validation result
318 * @throws PfModelException if an error occurred
320 private ValidationResult validatePolicies(SessionData data, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
321 throws PfModelException {
323 // build a map of the DB data, from policy name to (fully qualified) policy
325 Map<String, String> dbname2vers = new HashMap<>();
326 dbsub.getPolicies().forEach(ident -> dbname2vers.put(ident.getName(), ident.getVersion()));
328 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
330 for (ToscaConceptIdentifier ident : subgrp.getPolicies()) {
331 // note: "ident" may not have a fully qualified version
333 String expectedVersion = dbname2vers.get(ident.getName());
334 if (expectedVersion != null) {
335 // policy exists in the DB list - compare the versions
336 validateVersion(expectedVersion, ident, result);
337 ident.setVersion(expectedVersion);
341 // policy doesn't appear in the DB's policy list - look it up
343 ToscaPolicy policy = data.getPolicy(new ToscaConceptIdentifierOptVersion(ident));
344 if (policy == null) {
345 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
348 } else if (!isPolicySupported(dbsub.getSupportedPolicyTypes(), policy.getTypeIdentifier())) {
349 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
350 "not a supported policy for the subgroup"));
353 // replace version with the fully qualified version from the policy
354 ident.setVersion(policy.getVersion());
362 * Determines if the new version matches the version in the DB.
364 * @param dbvers fully qualified version from the DB
365 * @param ident identifier whose version is to be validated; the version need not be
367 * @param result the validation result
369 private void validateVersion(String dbvers, ToscaConceptIdentifier ident, BeanValidationResult result) {
370 String idvers = ident.getVersion();
371 if (dbvers.equals(idvers)) {
375 // did not match - see if it's a prefix
377 if (SessionData.isVersionPrefix(idvers) && dbvers.startsWith(idvers + ".")) {
378 // ident has a prefix of this version
382 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
383 "different version already deployed: " + dbvers));
387 * Deploys or updates PDP policies using the simple API.
389 * @param policies PDP policies
390 * @throws PfModelException if an error occurred
392 public void deployPolicies(PdpDeployPolicies policies) throws PfModelException {
394 MyPdpDeployPolicies checked = coder.convert(policies, MyPdpDeployPolicies.class);
395 ValidationResult result = new BeanValidator().validateTop(PdpDeployPolicies.class.getSimpleName(), checked);
396 if (!result.isValid()) {
397 String msg = result.getResult().trim();
398 throw new PfModelException(Status.BAD_REQUEST, msg);
400 } catch (CoderException e) {
401 throw new PfModelException(Status.INTERNAL_SERVER_ERROR, "cannot decode request", e);
404 process(policies, this::deploySimplePolicies);
408 * Deploys or updates PDP policies using the simple API. This is the method that does
411 * @param data session data
412 * @param extPolicies external PDP policies
413 * @return a list of requests that should be sent to configure the PDPs
414 * @throws PfModelException if an error occurred
416 private void deploySimplePolicies(SessionData data, PdpDeployPolicies policies) throws PfModelException {
418 for (ToscaConceptIdentifierOptVersion desiredPolicy : policies.getPolicies()) {
421 processPolicy(data, desiredPolicy);
423 } catch (PfModelException | RuntimeException e) {
424 // no need to log the error here, as it will be logged by the invoker
425 logger.warn("failed to deploy policy: {}", desiredPolicy);
432 * Adds a policy to a subgroup, if it isn't there already.
435 protected Updater makeUpdater(SessionData data, ToscaPolicy policy,
436 ToscaConceptIdentifierOptVersion requestedIdent) {
438 ToscaConceptIdentifier desiredIdent = policy.getIdentifier();
439 ToscaConceptIdentifier desiredType = policy.getTypeIdentifier();
441 return (group, subgroup) -> {
443 if (!isPolicySupported(subgroup.getSupportedPolicyTypes(), desiredType)) {
444 // doesn't support the desired policy type
448 if (containsPolicy(group, subgroup, desiredIdent)) {
452 if (subgroup.getPdpInstances().isEmpty()) {
453 throw new PfModelRuntimeException(Status.BAD_REQUEST, "group " + group.getName() + " subgroup "
454 + subgroup.getPdpType() + " has no active PDPs");
458 // add the policy to the subgroup
459 subgroup.getPolicies().add(desiredIdent);
461 logger.info("add policy {} to subgroup {} {} count={}", desiredIdent, group.getName(),
462 subgroup.getPdpType(), subgroup.getPolicies().size());
464 Set<String> pdps = subgroup.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
465 ToscaPolicy policyToBeDeployed = data.getPolicy(new ToscaConceptIdentifierOptVersion(desiredIdent));
466 data.trackDeploy(policyToBeDeployed, pdps, group.getName(), subgroup.getPdpType());
473 * Determines if a policy type is supported.
475 * @param supportedTypes supported policy types, any of which may end with ".*"
476 * @param desiredType policy type of interest
477 * @return {@code true} if the policy type is supported, {@code false} otherwise
479 private boolean isPolicySupported(List<ToscaConceptIdentifier> supportedTypes,
480 ToscaConceptIdentifier desiredType) {
482 if (supportedTypes.contains(desiredType)) {
486 String desiredTypeName = desiredType.getName();
487 for (ToscaConceptIdentifier type : supportedTypes) {
488 String supType = type.getName();
489 if (supType.endsWith(".*") && desiredTypeName.startsWith(supType.substring(0, supType.length() - 1))) {
490 // matches everything up to, AND INCLUDING, the "."
499 * Determines if a subgroup already contains the desired policy.
501 * @param group group that contains the subgroup
502 * @param subgroup subgroup of interest
503 * @param desiredIdent identifier of the desired policy
504 * @return {@code true} if the subgroup contains the desired policy, {@code false}
506 * @throws PfModelRuntimeException if the subgroup contains a different version of the
509 private boolean containsPolicy(PdpGroup group, PdpSubGroup subgroup, ToscaConceptIdentifier desiredIdent) {
510 String desnm = desiredIdent.getName();
511 String desvers = desiredIdent.getVersion();
513 for (ToscaConceptIdentifier actualIdent : subgroup.getPolicies()) {
514 if (!actualIdent.getName().equals(desnm)) {
518 // found the policy - ensure the version matches
519 if (!actualIdent.getVersion().equals(desvers)) {
520 throw new PfModelRuntimeException(Status.BAD_REQUEST,
521 "group " + group.getName() + " subgroup " + subgroup.getPdpType() + " policy " + desnm
522 + " " + desvers + " different version already deployed: "
523 + actualIdent.getVersion());
526 // already has the desired policy & version
527 logger.info("subgroup {} {} already contains policy {}", group.getName(), subgroup.getPdpType(),
536 * These are only used to validate the incoming request.
540 public static class MyPdpDeployPolicies {
542 private List<@NotNull @Valid PolicyIdent> policies;
546 public static class PolicyIdent {
547 @SerializedName("policy-id")
549 @Pattern(regexp = PfKey.NAME_REGEXP)
552 @SerializedName("policy-version")
553 @Pattern(regexp = "\\d+([.]\\d+[.]\\d+)?")
554 private String version;