Added the new versioning validation for policy and policy type
[policy/api.git] / main / src / main / java / org / onap / policy / api / main / rest / provider / PolicyProvider.java
1 /*-\r
2  * ============LICENSE_START=======================================================\r
3  * ONAP Policy API\r
4  * ================================================================================\r
5  * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.\r
6  * ================================================================================\r
7  * Licensed under the Apache License, Version 2.0 (the "License");\r
8  * you may not use this file except in compliance with the License.\r
9  * You may obtain a copy of the License at\r
10  *\r
11  *      http://www.apache.org/licenses/LICENSE-2.0\r
12  *\r
13  * Unless required by applicable law or agreed to in writing, software\r
14  * distributed under the License is distributed on an "AS IS" BASIS,\r
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16  * See the License for the specific language governing permissions and\r
17  * limitations under the License.\r
18  *\r
19  * SPDX-License-Identifier: Apache-2.0\r
20  * ============LICENSE_END=========================================================\r
21  */\r
22 \r
23 package org.onap.policy.api.main.rest.provider;\r
24 \r
25 import java.util.ArrayList;\r
26 import java.util.HashMap;\r
27 import java.util.List;\r
28 import java.util.Map;\r
29 import java.util.Map.Entry;\r
30 import javax.ws.rs.core.Response;\r
31 \r
32 import org.apache.commons.lang3.tuple.Pair;\r
33 import org.onap.policy.models.base.PfConceptKey;\r
34 import org.onap.policy.models.base.PfModelException;\r
35 import org.onap.policy.models.pdp.concepts.PdpGroup;\r
36 import org.onap.policy.models.pdp.concepts.PdpGroupFilter;\r
37 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;\r
38 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter;\r
39 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;\r
40 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;\r
41 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;\r
42 \r
43 /**\r
44  * Class to provide all kinds of policy operations.\r
45  *\r
46  * @author Chenfei Gao (cgao@research.att.com)\r
47  */\r
48 public class PolicyProvider extends CommonModelProvider {\r
49 \r
50     /**\r
51      * Default constructor.\r
52      */\r
53     public PolicyProvider() throws PfModelException {\r
54         super();\r
55     }\r
56 \r
57     /**\r
58      * Retrieves a list of policies matching specified ID and version of both policy type and policy.\r
59      *\r
60      * @param policyTypeId the ID of policy type\r
61      * @param policyTypeVersion the version of policy type\r
62      * @param policyId the ID of policy\r
63      * @param policyVersion the version of policy\r
64      *\r
65      * @return the ToscaServiceTemplate object\r
66      *\r
67      * @throws PfModelException the PfModel parsing exception\r
68      */\r
69     public ToscaServiceTemplate fetchPolicies(String policyTypeId, String policyTypeVersion,\r
70             String policyId, String policyVersion) throws PfModelException {\r
71 \r
72         ToscaServiceTemplate serviceTemplate = getFilteredPolicies(\r
73                 policyTypeId, policyTypeVersion, policyId, policyVersion);\r
74 \r
75         if (!hasPolicy(serviceTemplate)) {\r
76             throw new PfModelException(Response.Status.NOT_FOUND,\r
77                     constructResourceNotFoundMessage(policyTypeId, policyTypeVersion, policyId, policyVersion));\r
78         }\r
79 \r
80         return serviceTemplate;\r
81     }\r
82 \r
83     /**\r
84      * Retrieves a list of policies with the latest versions that match specified policy type id and version.\r
85      *\r
86      * @param policyTypeId the ID of policy type\r
87      * @param policyTypeVersion the version of policy type\r
88      * @param policyId the ID of the policy\r
89      *\r
90      * @return the ToscaServiceTemplate object\r
91      *\r
92      * @throws PfModelException the PfModel parsing exception\r
93      */\r
94     public ToscaServiceTemplate fetchLatestPolicies(String policyTypeId, String policyTypeVersion,\r
95             String policyId) throws PfModelException {\r
96 \r
97         ToscaServiceTemplate serviceTemplate = getFilteredPolicies(\r
98                 policyTypeId, policyTypeVersion, policyId, ToscaPolicyFilter.LATEST_VERSION);\r
99 \r
100         if (!hasPolicy(serviceTemplate)) {\r
101             throw new PfModelException(Response.Status.NOT_FOUND,\r
102                     constructResourceNotFoundMessage(policyTypeId, policyTypeVersion, policyId, null));\r
103         }\r
104 \r
105         return serviceTemplate;\r
106     }\r
107 \r
108     /**\r
109      * Retrieves a list of deployed policies in each pdp group.\r
110      *\r
111      * @param policyTypeId the ID of policy type\r
112      * @param policyTypeVersion the version of policy type\r
113      * @param policyId the ID of the policy\r
114      *\r
115      * @return a list of deployed policies in each pdp group\r
116      *\r
117      * @throws PfModelException the PfModel parsing exception\r
118      */\r
119     public Map<Pair<String, String>, List<ToscaPolicy>> fetchDeployedPolicies(\r
120             String policyTypeId, String policyTypeVersion, String policyId) throws PfModelException {\r
121 \r
122         return collectDeployedPolicies(policyId, new PfConceptKey(policyTypeId, policyTypeVersion),\r
123                 modelsProvider::getPolicyList, List::addAll, new ArrayList<>(5));\r
124     }\r
125 \r
126     /**\r
127      * Creates one or more new policies for the same policy type ID and version.\r
128      *\r
129      * @param policyTypeId the ID of policy type\r
130      * @param policyTypeVersion the version of policy type\r
131      * @param body the entity body of polic(ies)\r
132      *\r
133      * @return the ToscaServiceTemplate object\r
134      *\r
135      * @throws PfModelException the PfModel parsing exception\r
136      */\r
137     public ToscaServiceTemplate createPolicy(String policyTypeId, String policyTypeVersion,\r
138                                              ToscaServiceTemplate body) throws PfModelException {\r
139 \r
140         validatePolicyTypeExist(policyTypeId, policyTypeVersion);\r
141         validatePolicyTypeMatch(policyTypeId, policyTypeVersion, body);\r
142         validatePolicyVersion(body);\r
143         return modelsProvider.createPolicies(body);\r
144     }\r
145 \r
146     /**\r
147      * Creates one or more new policies.\r
148      *\r
149      * @param body the entity body of policy\r
150      *\r
151      * @return the ToscaServiceTemplate object\r
152      *\r
153      * @throws PfModelException the PfModel parsing exception\r
154      */\r
155     public ToscaServiceTemplate createPolicies(ToscaServiceTemplate body) throws PfModelException {\r
156 \r
157         validatePolicyVersion(body);\r
158         return modelsProvider.createPolicies(body);\r
159     }\r
160 \r
161     /**\r
162      * Deletes the policy matching specified ID and version of both policy type and policy.\r
163      *\r
164      * @param policyTypeId the ID of policy type\r
165      * @param policyTypeVersion the version of policy type\r
166      * @param policyId the ID of policy\r
167      * @param policyVersion the version of policy\r
168      *\r
169      * @return the ToscaServiceTemplate object\r
170      *\r
171      * @throws PfModelException the PfModel parsing exception\r
172      */\r
173     public ToscaServiceTemplate deletePolicy(String policyTypeId, String policyTypeVersion,\r
174                                  String policyId, String policyVersion) throws PfModelException {\r
175 \r
176         validateDeleteEligibility(policyTypeId, policyTypeVersion, policyId, policyVersion);\r
177 \r
178         ToscaServiceTemplate serviceTemplate = modelsProvider.deletePolicy(policyId, policyVersion);\r
179 \r
180         if (!hasPolicy(serviceTemplate)) {\r
181             throw new PfModelException(Response.Status.NOT_FOUND,\r
182                     constructResourceNotFoundMessage(policyTypeId, policyTypeVersion, policyId, policyVersion));\r
183         }\r
184 \r
185         return serviceTemplate;\r
186     }\r
187 \r
188     /**\r
189      * Validates whether policy type exists.\r
190      *\r
191      * @param policyTypeId the ID of policy type\r
192      * @param policyTypeVersion the version of policy type\r
193      *\r
194      * @throws PfModelException the PfModel parsing exception\r
195      */\r
196     private void validatePolicyTypeExist(String policyTypeId, String policyTypeVersion) throws PfModelException {\r
197 \r
198         ToscaServiceTemplate serviceTemplate = modelsProvider.getPolicyTypes(policyTypeId, policyTypeVersion);\r
199         if (!hasPolicyType(serviceTemplate)) {\r
200             throw new PfModelException(Response.Status.NOT_FOUND,\r
201                     "policy type with ID " + policyTypeId + ":" + policyTypeVersion + " does not exist");\r
202         }\r
203     }\r
204 \r
205     /**\r
206      * Validates the match between policy type specified in path and the one specified in type of policy.\r
207      *\r
208      * @param policyTypeId the ID of policy type\r
209      * @param policyTypeVersion the version of policy type\r
210      * @param serviceTemplate the ToscaServiceTemplate to validate\r
211      *\r
212      * @throws PfModelException the PfModel parsing exception\r
213      */\r
214     private void validatePolicyTypeMatch(String policyTypeId, String policyTypeVersion,\r
215             ToscaServiceTemplate serviceTemplate) throws PfModelException {\r
216 \r
217         List<Map<String, ToscaPolicy>> policies = serviceTemplate.getToscaTopologyTemplate().getPolicies();\r
218         for (Map<String, ToscaPolicy> policy : policies) {\r
219             if (policy.size() > 1) {\r
220                 throw new PfModelException(Response.Status.BAD_REQUEST,\r
221                         "one policy block contains more than one policies");\r
222             }\r
223             ToscaPolicy policyContent = policy.values().iterator().next();\r
224             if (!policyTypeId.equalsIgnoreCase(policyContent.getType())) {\r
225                 throw new PfModelException(Response.Status.BAD_REQUEST, "policy type id does not match");\r
226             }\r
227             if (policyContent.getTypeVersion() != null\r
228                     && !policyTypeVersion.equalsIgnoreCase(policyContent.getTypeVersion())) {\r
229                 throw new PfModelException(Response.Status.BAD_REQUEST, "policy type version does not match");\r
230             }\r
231         }\r
232     }\r
233 \r
234     /**\r
235      * Validates whether specified policy can be deleted based on the rule that deployed policy cannot be deleted.\r
236      *\r
237      * @param policyTypeId the ID of policy type\r
238      * @param policyTypeVersion the version of policy type\r
239      * @param policyId the ID of policy\r
240      * @param policyVersion the version of policy\r
241      *\r
242      * @throws PfModelException the PfModel parsing exception\r
243      */\r
244     private void validateDeleteEligibility(String policyTypeId, String policyTypeVersion,\r
245             String policyId, String policyVersion) throws PfModelException {\r
246 \r
247         List<ToscaPolicyTypeIdentifier> policyTypes = new ArrayList<>(1);\r
248         policyTypes.add(new ToscaPolicyTypeIdentifier(policyTypeId, policyTypeVersion));\r
249         List<ToscaPolicyIdentifier> policies = new ArrayList<>(1);\r
250         policies.add(new ToscaPolicyIdentifier(policyId, policyVersion));\r
251         PdpGroupFilter pdpGroupFilter = PdpGroupFilter.builder()\r
252                 .policyTypeList(policyTypes).policyList(policies).build();\r
253 \r
254         List<PdpGroup> pdpGroups = modelsProvider.getFilteredPdpGroups(pdpGroupFilter);\r
255 \r
256         if (!pdpGroups.isEmpty()) {\r
257             throw new PfModelException(Response.Status.CONFLICT,\r
258                     constructDeletePolicyViolationMessage(policyId, policyVersion, pdpGroups));\r
259         }\r
260     }\r
261 \r
262     /**\r
263      * Validates the provided policy version in the payload.\r
264      *\r
265      * @param body the provided TOSCA service template which contains the policies\r
266      *\r
267      * @throws PfModelException the PfModel parsing exception\r
268      */\r
269     private void validatePolicyVersion(ToscaServiceTemplate body) throws PfModelException {\r
270 \r
271         validatePolicyVersionExist(body);\r
272         validateNoDuplicateVersionInDb(body);\r
273         validateNoDuplicateVersionInPl(body);\r
274     }\r
275 \r
276     /**\r
277      * Validates the existence of at least one policies in the provided payload.\r
278      *\r
279      * @param body the TOSCA service template payload to check against\r
280      *\r
281      * @throws PfModelException the PfModel parsing exception\r
282      */\r
283     private void validatePolicyVersionExist(ToscaServiceTemplate body) throws PfModelException {\r
284 \r
285         List<String> invalidPolicyNames = new ArrayList<>(body.getToscaTopologyTemplate().getPolicies().size());\r
286         for (Map<String, ToscaPolicy> policy : body.getToscaTopologyTemplate().getPolicies()) {\r
287             ToscaPolicy policyContent = policy.values().iterator().next();\r
288             if (policyContent.getVersion() == null) {\r
289                 invalidPolicyNames.add(policy.keySet().iterator().next());\r
290             }\r
291         }\r
292 \r
293         if (!invalidPolicyNames.isEmpty()) {\r
294             String errMsg = "mandatory 'version' field is missing in policies: "\r
295                 + String.join(", ", invalidPolicyNames);\r
296             throw new PfModelException(Response.Status.NOT_ACCEPTABLE, errMsg);\r
297         }\r
298     }\r
299 \r
300     /**\r
301      * Validates there is no duplicate policy version stored in the database.\r
302      *\r
303      * @param body the TOSCA service template payload to check against\r
304      *\r
305      * @throws PfModelException the PfModel parsing exception\r
306      */\r
307     private void validateNoDuplicateVersionInDb(ToscaServiceTemplate body) throws PfModelException {\r
308 \r
309         Map<String, String> invalidPolicies = new HashMap<>();\r
310         for (Map<String, ToscaPolicy> policy: body.getToscaTopologyTemplate().getPolicies()) {\r
311             ToscaPolicy policyContent = policy.values().iterator().next();\r
312             String policyName = policy.keySet().iterator().next();\r
313             String policyVersion = policyContent.getVersion();\r
314             String policyTypeName = policyContent.getType();\r
315             String policyTypeVersion = policyContent.getTypeVersion();\r
316             ToscaServiceTemplate serviceTemplate =\r
317                     getFilteredPolicies(policyTypeName, policyTypeVersion, policyName, policyVersion);\r
318             if (hasPolicy(serviceTemplate)) {\r
319                 String latestVersion = getFilteredPolicies(policyTypeName, policyTypeVersion,\r
320                         policyName, ToscaPolicyFilter.LATEST_VERSION).getToscaTopologyTemplate().getPoliciesAsMap()\r
321                         .values().iterator().next().getVersion();\r
322                 invalidPolicies.put(String.join(":", policyName, policyVersion), latestVersion);\r
323             }\r
324         }\r
325 \r
326         if (!invalidPolicies.isEmpty()) {\r
327             List<String> duplicateVersions = new ArrayList<>(5);\r
328             for (Entry<String, String> invalidPolicy : invalidPolicies.entrySet()) {\r
329                 String eachDuplicateVersion = "policy " + invalidPolicy.getKey()\r
330                     + " already exists; its latest version is " + invalidPolicy.getValue();\r
331                 duplicateVersions.add(eachDuplicateVersion);\r
332             }\r
333             throw new PfModelException(Response.Status.NOT_ACCEPTABLE, String.join("\n", duplicateVersions));\r
334         }\r
335     }\r
336 \r
337     /**\r
338      * Validates there is no duplicate policy version in the provided payload if multiple policies are present.\r
339      *\r
340      * @param body the TOSCA service template payload to check against\r
341      *\r
342      * @throws PfModelException the PfModel parsing exception\r
343      */\r
344     private void validateNoDuplicateVersionInPl(ToscaServiceTemplate body) throws PfModelException {\r
345 \r
346         List<Map<String, ToscaPolicy>> policies = body.getToscaTopologyTemplate().getPolicies();\r
347         List<String> duplicateVersions = new ArrayList<>();\r
348         for (int i = 0; i < policies.size() - 1; i++) {\r
349             for (int j = i + 1; j < policies.size(); j++) {\r
350                 if (hasSameNameVersion(policies.get(i), policies.get(j))) {\r
351                     String nameVersion = policies.get(i).keySet().iterator().next() + ":"\r
352                         + policies.get(i).values().iterator().next().getVersion();\r
353                     duplicateVersions.add(nameVersion);\r
354                 }\r
355             }\r
356         }\r
357 \r
358         if (!duplicateVersions.isEmpty()) {\r
359             String errMsg = "the same version of policies '" + String.join(", ", duplicateVersions)\r
360                 + "' appear multiple times in the payload";\r
361             throw new PfModelException(Response.Status.NOT_ACCEPTABLE, errMsg);\r
362         }\r
363     }\r
364 \r
365     /**\r
366      * Checks if two policies have the same name and version.\r
367      *\r
368      * @param policy1 the first policy\r
369      * @param policy2 the second policy\r
370      *\r
371      * @return the boolean flag to indicate the result\r
372      */\r
373     private boolean hasSameNameVersion(Map<String, ToscaPolicy> policy1, Map<String, ToscaPolicy> policy2) {\r
374 \r
375         return (policy1.keySet().iterator().next().equals(policy2.keySet().iterator().next())\r
376                 && policy1.values().iterator().next().getVersion().equals(\r
377                         policy2.values().iterator().next().getVersion()));\r
378     }\r
379 \r
380     /**\r
381      * Retrieves the specified version of the policy.\r
382      *\r
383      * @param policyTypeName the name of the policy type\r
384      * @param policyTypeVersion the version of the policy type\r
385      * @param policyName the name of the policy\r
386      * @param policyVersion the version of the policy\r
387      *\r
388      * @return the TOSCA service template containing the specified version of the policy\r
389      *\r
390      * @throws PfModelException the PfModel parsing exception\r
391      */\r
392     private ToscaServiceTemplate getFilteredPolicies(String policyTypeName, String policyTypeVersion,\r
393             String policyName, String policyVersion) throws PfModelException {\r
394 \r
395         ToscaPolicyFilter policyFilter = ToscaPolicyFilter.builder()\r
396                 .name(policyName).version(policyVersion)\r
397                 .type(policyTypeName).typeVersion(policyTypeVersion).build();\r
398         return modelsProvider.getFilteredPolicies(policyFilter);\r
399     }\r
400 \r
401     /**\r
402      * Constructs returned message for not found resource.\r
403      *\r
404      * @param policyTypeId the ID of policy type\r
405      * @param policyTypeVersion the version of policy type\r
406      * @param policyId the ID of policy\r
407      * @param policyVersion the version of policy\r
408      *\r
409      * @return constructed message\r
410      */\r
411     private String constructResourceNotFoundMessage(String policyTypeId, String policyTypeVersion,\r
412             String policyId, String policyVersion) {\r
413 \r
414         return "policy with ID " + policyId + ":" + policyVersion\r
415                 + " and type " + policyTypeId + ":" + policyTypeVersion + " does not exist";\r
416     }\r
417 }\r