More examples of optimization policies and cleanup
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / std / StdMatchableTranslator.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.pdp.xacml.application.common.std;
24
25 import com.att.research.xacml.api.Identifier;
26 import com.att.research.xacml.api.Request;
27 import com.att.research.xacml.api.XACML3;
28 import com.att.research.xacml.std.IdentifierImpl;
29 import com.att.research.xacml.util.XACMLPolicyWriter;
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Map.Entry;
44 import lombok.Setter;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
51 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
52 import org.onap.policy.common.utils.coder.CoderException;
53 import org.onap.policy.common.utils.coder.StandardCoder;
54 import org.onap.policy.common.utils.coder.StandardYamlCoder;
55 import org.onap.policy.models.decisions.concepts.DecisionRequest;
56 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
57 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
58 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
59 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
61 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
62 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
63 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
64 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
65 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
66 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
67 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 /**
72  * This standard matchable translator uses Policy Types that contain "matchable" field in order
73  * to translate policies.
74  *
75  * @author pameladragosh
76  *
77  */
78 public class StdMatchableTranslator  extends StdBaseTranslator {
79
80     private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
81     private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
82
83     private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> matchablePolicyTypes = new HashMap<>();
84     @Setter
85     private RestServerParameters apiRestParameters;
86     @Setter
87     private Path pathForData;
88
89     public StdMatchableTranslator() {
90         super();
91     }
92
93     @Override
94     public Request convertRequest(DecisionRequest request) {
95         LOGGER.info("Converting Request {}", request);
96         try {
97             return StdMatchablePolicyRequest.createInstance(request);
98         } catch (XacmlApplicationException e) {
99             LOGGER.error("Failed to convert DecisionRequest: {}", e);
100         }
101         //
102         // TODO throw exception
103         //
104         return null;
105     }
106
107     @Override
108     public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
109         //
110         // Get the TOSCA Policy Type for this policy
111         //
112         Collection<ToscaPolicyType> toscaPolicyTypes = this.getPolicyTypes(toscaPolicy.getTypeIdentifier());
113         //
114         // If we don't have any TOSCA policy types, then we cannot know
115         // which properties are matchable.
116         //
117         if (toscaPolicyTypes.isEmpty()) {
118             throw new ToscaPolicyConversionException(
119                     "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
120         }
121         //
122         // Policy name should be at the root
123         //
124         String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
125         //
126         // Set it as the policy ID
127         //
128         PolicyType newPolicyType = new PolicyType();
129         newPolicyType.setPolicyId(policyName);
130         //
131         // Optional description
132         //
133         newPolicyType.setDescription(toscaPolicy.getDescription());
134         //
135         // There should be a metadata section
136         //
137         fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
138         //
139         // Set the combining rule
140         //
141         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
142         //
143         // Generate the TargetType - the policy should not be evaluated
144         // unless all the matchable properties it cares about are matched.
145         //
146         newPolicyType.setTarget(generateTargetType(toscaPolicy.getProperties(), toscaPolicyTypes));
147         //
148         // Now represent the policy as Json
149         //
150         StandardCoder coder = new StandardCoder();
151         String jsonPolicy;
152         try {
153             jsonPolicy = coder.encode(toscaPolicy);
154         } catch (CoderException e) {
155             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
156         }
157         //
158         // Add it as an obligation
159         //
160         addObligation(newPolicyType, jsonPolicy);
161         //
162         // Now create the Permit Rule.
163         //
164         RuleType rule = new RuleType();
165         rule.setDescription("Default is to PERMIT if the policy matches.");
166         rule.setRuleId(policyName + ":rule");
167         rule.setEffect(EffectType.PERMIT);
168         rule.setTarget(new TargetType());
169         //
170         // The rule contains the Condition which adds logic for
171         // optional policy-type filtering.
172         //
173         rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
174         //
175         // Add the rule to the policy
176         //
177         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
178         //
179         // Log output of the policy
180         //
181         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
182             XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
183             LOGGER.info("{}", os);
184         } catch (IOException e) {
185             LOGGER.error("Failed to create byte array stream", e);
186         }
187         //
188         // Done
189         //
190         return newPolicyType;
191     }
192
193     /**
194      * For generating target type, we scan for matchable properties
195      * and use those to build the policy.
196      *
197      * @param properties Properties section of policy
198      * @param policyTypes Collection of policy Type to find matchable metadata
199      * @return TargetType object
200      */
201     protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
202         TargetType targetType = new TargetType();
203         //
204         // Iterate the properties
205         //
206         for (Entry<String, Object> entrySet : properties.entrySet()) {
207             //
208             // Find matchable properties
209             //
210             if (isMatchable(entrySet.getKey(), policyTypes)) {
211                 LOGGER.info("Found matchable property {}", entrySet.getKey());
212                 generateMatchable(targetType, entrySet.getKey(), entrySet.getValue());
213             }
214         }
215
216         return targetType;
217     }
218
219     /**
220      * isMatchable - Iterates through available TOSCA Policy Types to determine if a property
221      * should be treated as matchable.
222      *
223      * @param propertyName Name of property
224      * @param policyTypes Collection of TOSCA Policy Types to scan
225      * @return true if matchable
226      */
227     protected boolean isMatchable(String propertyName, Collection<ToscaPolicyType> policyTypes) {
228         for (ToscaPolicyType policyType : policyTypes) {
229             for (Entry<String, ToscaProperty> propertiesEntry : policyType.getProperties().entrySet()) {
230                 if (checkIsMatchableProperty(propertyName, propertiesEntry)) {
231                     return true;
232                 }
233             }
234         }
235         return false;
236     }
237
238     /**
239      * checkIsMatchableProperty - checks the policy contents for matchable field. If the metadata doesn't exist,
240      * then definitely not. If the property doesn't exist, then definitely not. Otherwise need to have a metadata
241      * section with the matchable property set to true.
242      *
243      * @param propertyName String value of property
244      * @param propertiesEntry Section of the TOSCA Policy Type where properties and metadata sections are held
245      * @return true if matchable
246      */
247     protected boolean checkIsMatchableProperty(String propertyName, Entry<String, ToscaProperty> propertiesEntry) {
248         if (! propertiesEntry.getKey().equals(propertyName)
249                 || propertiesEntry.getValue().getMetadata() == null) {
250             return false;
251         }
252         for (Entry<String, String> entrySet : propertiesEntry.getValue().getMetadata().entrySet()) {
253             if ("matchable".equals(entrySet.getKey()) && "true".equals(entrySet.getValue())) {
254                 return true;
255             }
256         }
257         return false;
258     }
259
260     /**
261      * generateMatchable - Given the object, generates list of MatchType objects and add them
262      * to the TargetType object.
263      *
264      * @param targetType TargetType object to add matches to
265      * @param key Property key
266      * @param value Object is the value - which can be a Collection or single Object
267      * @return TargetType incoming TargetType returned as a convenience
268      */
269     @SuppressWarnings("unchecked")
270     protected TargetType generateMatchable(TargetType targetType, String key, Object value) {
271         if (value instanceof Collection) {
272             AnyOfType anyOf = generateMatches((Collection<Object>) value,
273                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
274             if (! anyOf.getAllOf().isEmpty()) {
275                 targetType.getAnyOf().add(anyOf);
276             }
277         } else {
278             AnyOfType anyOf = generateMatches(Arrays.asList(value),
279                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
280             if (! anyOf.getAllOf().isEmpty()) {
281                 targetType.getAnyOf().add(anyOf);
282             }
283         }
284         return targetType;
285     }
286
287     /**
288      * generateMatches - Goes through the collection of objects, creates a MatchType object
289      * for each object and associates it with the given attribute Id. Returns the AnyOfType
290      * object that contains all the generated MatchType objects.
291      *
292      * @param matchables Collection of object to generate MatchType from
293      * @param attributeId Given attribute Id for each MatchType
294      * @return AnyOfType object
295      */
296     protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
297         //
298         // This is our outer AnyOf - which is an OR
299         //
300         AnyOfType anyOf = new AnyOfType();
301         for (Object matchable : matchables) {
302             //
303             // Default to string
304             //
305             Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
306             Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
307             //
308             // See if we are another datatype
309             //
310             // We should add datetime support. But to do that we need
311             // probably more metadata to describe how that would be translated.
312             //
313             if (matchable instanceof Integer) {
314                 idFunction = XACML3.ID_FUNCTION_INTEGER_EQUAL;
315                 idDatatype = XACML3.ID_DATATYPE_INTEGER;
316             } else if (matchable instanceof Double) {
317                 idFunction = XACML3.ID_FUNCTION_DOUBLE_EQUAL;
318                 idDatatype = XACML3.ID_DATATYPE_DOUBLE;
319             } else if (matchable instanceof Boolean) {
320                 idFunction = XACML3.ID_FUNCTION_BOOLEAN_EQUAL;
321                 idDatatype = XACML3.ID_DATATYPE_BOOLEAN;
322             }
323             //
324             // Create a match for this
325             //
326             MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
327                     idFunction,
328                     matchable.toString(),
329                     idDatatype,
330                     attributeId,
331                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
332             //
333             // Now create an anyOf (OR)
334             //
335             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
336         }
337         return anyOf;
338     }
339
340     /**
341      * Get Policy Type definitions. This could be previously loaded, or could be
342      * stored in application path, or may need to be pulled from the API.
343      *
344      *
345      * @param policyTypeId Policy Type Id
346      * @return A list of PolicyTypes
347      */
348     private List<ToscaPolicyType> getPolicyTypes(ToscaPolicyTypeIdentifier policyTypeId) {
349         //
350         // Create identifier from the policy
351         //
352         ToscaPolicyTypeIdentifier typeId = new ToscaPolicyTypeIdentifier(policyTypeId);
353         //
354         // Find the Policy Type
355         //
356         ToscaPolicyType policyType = findPolicyType(typeId);
357         if (policyType == null)  {
358             return Collections.emptyList();
359         }
360         //
361         // Create our return object
362         //
363         List<ToscaPolicyType> listTypes = new ArrayList<>();
364         listTypes.add(policyType);
365         //
366         // Look for parent policy types that could also contain matchable properties
367         //
368         ToscaPolicyType childPolicyType = policyType;
369         while (! childPolicyType.getDerivedFrom().startsWith("tosca.policies.Root")) {
370             //
371             // Create parent policy type id.
372             //
373             // We will have to assume the same version between child and the
374             // parent policy type it derives from.
375             //
376             // Or do we assume 1.0.0?
377             //
378             ToscaPolicyTypeIdentifier parentId = new ToscaPolicyTypeIdentifier(childPolicyType.getDerivedFrom(),
379                     "1.0.0");
380             //
381             // Find the policy type
382             //
383             ToscaPolicyType parentPolicyType = findPolicyType(parentId);
384             if (parentPolicyType == null) {
385                 //
386                 // Probably would be best to throw an exception and
387                 // return nothing back.
388                 //
389                 // But instead we will log a warning
390                 //
391                 LOGGER.warn("Missing parent policy type - proceeding anyway {}", parentId);
392                 //
393                 // Break the loop
394                 //
395                 break;
396             }
397             //
398             // Great save it
399             //
400             listTypes.add(parentPolicyType);
401             //
402             // Move to the next parent
403             //
404             childPolicyType = parentPolicyType;
405         }
406         return listTypes;
407     }
408
409     /**
410      * findPolicyType - given the ToscaPolicyTypeIdentifier, finds it in memory, or
411      * then tries to find it either locally on disk or pull it from the Policy
412      * Lifecycle API the given TOSCA Policy Type.
413      *
414      * @param policyTypeId ToscaPolicyTypeIdentifier to find
415      * @return ToscaPolicyType object. Can be null if failure.
416      */
417     private ToscaPolicyType findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
418         //
419         // Is it loaded in memory?
420         //
421         ToscaPolicyType policyType = this.matchablePolicyTypes.get(policyTypeId);
422         if (policyType == null)  {
423             //
424             // Load the policy
425             //
426             policyType = this.loadPolicyType(policyTypeId);
427         }
428         //
429         // Yep return it
430         //
431         return policyType;
432     }
433
434     /**
435      * loadPolicyType - Tries to load the given ToscaPolicyTypeIdentifier from local
436      * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
437      * API.
438      *
439      * @param policyTypeId ToscaPolicyTypeIdentifier input
440      * @return ToscaPolicyType object. Null if failure.
441      */
442     private ToscaPolicyType loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
443         //
444         // Construct what the file name should be
445         //
446         Path policyTypePath = this.constructLocalFilePath(policyTypeId);
447         //
448         // See if it exists
449         //
450         byte[] bytes;
451         try {
452             //
453             // If it exists locally, read the bytes in
454             //
455             bytes = Files.readAllBytes(policyTypePath);
456         } catch (IOException e) {
457             //
458             // Does not exist locally, so let's GET it from the policy api
459             //
460             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
461             //
462             // So let's pull it from API REST call and save it locally
463             //
464             return this.pullPolicyType(policyTypeId, policyTypePath);
465         }
466         //
467         // Success - we have read locally the policy type. Now bring it into our
468         // return object.
469         //
470         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
471         try {
472             ToscaServiceTemplate serviceTemplate = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
473                     ToscaServiceTemplate.class);
474             JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
475             jtst.fromAuthorative(serviceTemplate);
476             ToscaServiceTemplate completedJtst = jtst.toAuthorative();
477             //
478             // Search for our Policy Type, there really only should be one but
479             // this is returned as a map.
480             //
481             for ( Entry<String, ToscaPolicyType> entrySet : completedJtst.getPolicyTypes().entrySet()) {
482                 ToscaPolicyType entryPolicyType = entrySet.getValue();
483                 if (policyTypeId.getName().equals(entryPolicyType.getName())
484                         && policyTypeId.getVersion().equals(entryPolicyType.getVersion())) {
485                     LOGGER.info("Found existing local policy type {} {}", entryPolicyType.getName(),
486                             entryPolicyType.getVersion());
487                     //
488                     // Just simply return the policy type right here
489                     //
490                     return entryPolicyType;
491                 } else {
492                     LOGGER.warn("local policy type contains different name version {} {}", entryPolicyType.getName(),
493                             entryPolicyType.getVersion());
494                 }
495             }
496             //
497             // This would be an error, if the file stored does not match what its supposed to be
498             //
499             LOGGER.error("Existing policy type file does not contain right name and version");
500         } catch (CoderException e) {
501             LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
502         }
503         //
504         // Hopefully we never get here
505         //
506         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
507         return null;
508     }
509
510     /**
511      * pullPolicyType - pulls the given ToscaPolicyTypeIdentifier from the Policy Lifecycle API.
512      * If successful, will store it locally given the policyTypePath.
513      *
514      * @param policyTypeId ToscaPolicyTypeIdentifier
515      * @param policyTypePath Path object to store locally
516      * @return ToscaPolicyType object. Null if failure.
517      */
518     private synchronized ToscaPolicyType pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId, Path policyTypePath) {
519         //
520         // This is what we return
521         //
522         ToscaPolicyType policyType = null;
523         try {
524             PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
525
526             policyType = api.getPolicyType(policyTypeId);
527         } catch (PolicyApiException e) {
528             LOGGER.error("Failed to make API call", e);
529             LOGGER.error("parameters: {} ", this.apiRestParameters);
530             return null;
531         }
532         //
533         // Store it locally
534         //
535         try {
536             standardYamlCoder.encode(policyTypePath.toFile(), policyType);
537         } catch (CoderException e) {
538             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
539         }
540         //
541         // Done return the policy type
542         //
543         return policyType;
544     }
545
546     /**
547      * constructLocalFilePath - common method to ensure the name of the local file for the
548      * policy type is the same.
549      *
550      * @param policyTypeId ToscaPolicyTypeIdentifier
551      * @return Path object
552      */
553     private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
554         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
555                 + policyTypeId.getVersion() + ".yaml");
556     }
557 }