b546d2606ac5dfbc7a1db184f8136a88ecccdfc7
[smo/teiv.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2024 Ericsson
4  *  Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21 package org.oran.smo.yangtools.parser;
22
23 import java.util.ArrayList;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28
29 import org.oran.smo.yangtools.parser.findings.Finding;
30 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
31 import org.oran.smo.yangtools.parser.model.ConformanceType;
32 import org.oran.smo.yangtools.parser.model.ModuleIdentity;
33 import org.oran.smo.yangtools.parser.model.YangModel;
34 import org.oran.smo.yangtools.parser.model.schema.Schema;
35 import org.oran.smo.yangtools.parser.yanglibrary.Datastore;
36 import org.oran.smo.yangtools.parser.yanglibrary.Module;
37 import org.oran.smo.yangtools.parser.yanglibrary.Submodule;
38 import org.oran.smo.yangtools.parser.yanglibrary.YangLibrary;
39
40 /**
41  * Checks the contents of a YANG Library (instance data) against a schema (and thereby the modules
42  * supplied by the client). This is useful to compare the modules supplied for a schema against a YL.
43  *
44  * @author Mark Hollmann
45  */
46 public class CheckYangLibraryAgainstSchema {
47
48     private final ParserExecutionContext parserContext;
49     private final Schema schema;
50     private final YangLibrary yangLibrary;
51
52     public CheckYangLibraryAgainstSchema(final ParserExecutionContext parserContext, final Schema schema,
53             final YangLibrary yangLibrary) {
54         this.parserContext = parserContext;
55         this.schema = schema;
56         this.yangLibrary = yangLibrary;
57     }
58
59     /**
60      * Checks the YL against the modules. Any findings will be added to the supplied findings manager.
61      */
62     public void performChecks() {
63
64         /*
65          * There has to be a running datastore.
66          */
67         final Datastore runningDatastore = yangLibrary.getRunningDatastore();
68         if (runningDatastore == null) {
69             parserContext.getFindingsManager().addFinding(new Finding(ParserFindingType.P081_INCORRECT_YANG_LIBRARY_DATA,
70                     "Could not find 'running' datastore in YANG library data."));
71             return;
72         }
73
74         /*
75          * Collect the identities of the modules listed in the Yang Library
76          */
77         final Set<Module> yangLibImplementingModules = runningDatastore.getImplementingModules();
78         final Set<Module> yangLibImportOnlyModules = runningDatastore.getImportOnlyModules();
79         final Set<Submodule> yangLibSubmodules = new HashSet<>();
80         yangLibImplementingModules.forEach(module -> yangLibSubmodules.addAll(module.getSubmodules()));
81         yangLibImportOnlyModules.forEach(module -> yangLibSubmodules.addAll(module.getSubmodules()));
82
83         final Set<ModuleIdentity> yangLibImplementingModuleIdentities = yangLibImplementingModules.stream().map(
84                 Module::getModuleIdentity).collect(Collectors.toSet());
85         final Set<ModuleIdentity> yangLibImportOnlyModuleIdentities = yangLibImportOnlyModules.stream().map(
86                 Module::getModuleIdentity).collect(Collectors.toSet());
87         final Set<ModuleIdentity> yangLibSubmoduleIdentities = yangLibSubmodules.stream().map(Submodule::getModuleIdentity)
88                 .collect(Collectors.toSet());
89
90         /*
91          * Collect the identities of the modules supplied to the parser
92          */
93         final List<YangModel> yangModels = new ArrayList<>(schema.getModuleRegistry().getAllYangModels());
94
95         final Set<ModuleIdentity> inputImplementingModuleIdentities = yangModels.stream().filter(input -> input
96                 .getYangModelRoot().isModule() && input.getConformanceType() == ConformanceType.IMPLEMENT).map(
97                         YangModel::getModuleIdentity).collect(Collectors.toSet());
98         final Set<ModuleIdentity> inputImportOnlyModuleIdentities = yangModels.stream().filter(input -> input
99                 .getYangModelRoot().isModule() && input.getConformanceType() == ConformanceType.IMPORT).map(
100                         YangModel::getModuleIdentity).collect(Collectors.toSet());
101         final Set<ModuleIdentity> inputSubmoduleIdentities = yangModels.stream().filter(input -> input.getYangModelRoot()
102                 .isSubmodule()).map(YangModel::getModuleIdentity).collect(Collectors.toSet());
103
104         /*
105          * Now we simply compare these. They must fully match up - no missing/superfluous YAMs; correct conformance type.
106          */
107         checkInputMatchesYangLibraryForModules(inputImplementingModuleIdentities, yangLibImplementingModuleIdentities,
108                 ConformanceType.IMPLEMENT);
109         checkInputMatchesYangLibraryForModules(inputImportOnlyModuleIdentities, yangLibImportOnlyModuleIdentities,
110                 ConformanceType.IMPORT);
111         checkInputMatchesYangLibraryForSubmodules(inputSubmoduleIdentities, yangLibSubmoduleIdentities);
112
113         /*
114          * The Yang Library will also list features and the namespaces. Check these match up.
115          */
116         checkFeaturesAndNamespaces();
117     }
118
119     /**
120      * Cross-check the modules listed in Yang Library against the YAMs in the input.
121      */
122     private void checkInputMatchesYangLibraryForModules(final Set<ModuleIdentity> inputModuleIdentities,
123             final Set<ModuleIdentity> yangLibModuleIdentities, final ConformanceType conformanceType) {
124
125         final Set<ModuleIdentity> inInputButNotYangLib = new HashSet<>(inputModuleIdentities);
126         inInputButNotYangLib.removeAll(yangLibModuleIdentities);
127
128         inInputButNotYangLib.forEach(mi -> {
129             final Finding finding = new Finding(ParserFindingType.P085_MISMATCH_BETWEEN_INPUT_MODULES_AND_YANG_LIBRARY,
130                     "Module '" + mi.toString() + "' with conformance type '" + conformanceType
131                             .toString() + "' is in the input, but not listed (or not with this conformance type) in the supplied YANG library.");
132             parserContext.getFindingsManager().addFinding(finding);
133         });
134
135         final Set<ModuleIdentity> inYangLibButNotInput = new HashSet<>(yangLibModuleIdentities);
136         inYangLibButNotInput.removeAll(inputModuleIdentities);
137
138         inYangLibButNotInput.forEach(mi -> {
139             final Finding finding = new Finding(ParserFindingType.P085_MISMATCH_BETWEEN_INPUT_MODULES_AND_YANG_LIBRARY,
140                     "Module '" + mi.toString() + "' with conformance type '" + conformanceType
141                             .toString() + "' is listed in the supplied YANG library, but could not be found (or not with this conformance type) in the input.");
142             parserContext.getFindingsManager().addFinding(finding);
143         });
144     }
145
146     /**
147      * Cross-check the submodules listed in Yang Library against the YAMs in the input.
148      */
149     private void checkInputMatchesYangLibraryForSubmodules(final Set<ModuleIdentity> inputSubmoduleIdentities,
150             final Set<ModuleIdentity> yangLibSubmoduleIdentities) {
151
152         final Set<ModuleIdentity> inInputButNotYangLib = new HashSet<>(inputSubmoduleIdentities);
153         inInputButNotYangLib.removeAll(yangLibSubmoduleIdentities);
154
155         inInputButNotYangLib.forEach(mi -> {
156             final Finding finding = new Finding(ParserFindingType.P085_MISMATCH_BETWEEN_INPUT_MODULES_AND_YANG_LIBRARY,
157                     "Submodule '" + mi.toString() + "' is in the input, but not listed in the supplied YANG library.");
158             parserContext.getFindingsManager().addFinding(finding);
159         });
160
161         final Set<ModuleIdentity> inYangLibButNotInput = new HashSet<>(yangLibSubmoduleIdentities);
162         inYangLibButNotInput.removeAll(inputSubmoduleIdentities);
163
164         inYangLibButNotInput.forEach(mi -> {
165             final Finding finding = new Finding(ParserFindingType.P085_MISMATCH_BETWEEN_INPUT_MODULES_AND_YANG_LIBRARY,
166                     "Submodule '" + mi
167                             .toString() + "' is listed in the supplied YANG library, but could not be found in the input.");
168             parserContext.getFindingsManager().addFinding(finding);
169         });
170     }
171
172     /**
173      * Checks the features listed inside the YANG Library against the features that are actually
174      * declared in the models. Also checks the namespace matches.
175      */
176     private void checkFeaturesAndNamespaces() {
177
178         final Set<Module> yangLibAllModules = yangLibrary.getRunningDatastore().getAllModules();
179         final List<YangModel> inputAllModules = schema.getModuleRegistry().getAllYangModels();
180
181         for (final Module yangLibOneModule : yangLibAllModules) {
182             final ModuleIdentity yangLibOneModuleIdentity = yangLibOneModule.getModuleIdentity();
183
184             for (final YangModel inputOneModule : inputAllModules) {
185                 if (yangLibOneModuleIdentity.equals(inputOneModule.getModuleIdentity())) {
186                     checkFeaturesAndNamespace(yangLibOneModule, inputOneModule);
187                     break;
188                 }
189             }
190         }
191     }
192
193     /**
194      * Checks that the features for a given module listed in the Yang Library actually exist inside the YAM.
195      * <p>
196      * Also checks the namespaces match up.
197      */
198     private void checkFeaturesAndNamespace(final Module yangLibOneModule, final YangModel inputOneModule) {
199
200         final Set<String> featuresDeclaredInInputModule = getFeaturesDeclaredInsideInputModule(inputOneModule);
201
202         yangLibOneModule.getFeatures().forEach(featureListedInYangLib -> {
203             if (!featuresDeclaredInInputModule.contains(featureListedInYangLib)) {
204                 final Finding finding = new Finding(ParserFindingType.P083_FEATURE_LISTED_IN_YANG_LIBRARY_NOT_FOUND,
205                         "YANG Library entry for module '" + yangLibOneModule
206                                 .getName() + "' lists feature '" + featureListedInYangLib + "', but this feature was not found in the actual module (or its submodules).");
207                 parserContext.getFindingsManager().addFinding(finding);
208             }
209         });
210
211         final String yangLibNamespace = yangLibOneModule.getNamespace();
212         final String inputNamespace = inputOneModule.getPrefixResolver().getDefaultNamespaceUri();
213
214         if (!yangLibNamespace.equals(inputNamespace)) {
215             final Finding finding = new Finding(ParserFindingType.P081_INCORRECT_YANG_LIBRARY_DATA,
216                     "Namespace mismatch for module '" + yangLibOneModule
217                             .getName() + "' between what is stated in the Yang Library and what is declaread in the module.");
218             parserContext.getFindingsManager().addFinding(finding);
219         }
220     }
221
222     /**
223      * Returns the features that are defined inside a module, or any of its submodules.
224      */
225     private Set<String> getFeaturesDeclaredInsideInputModule(final YangModel yangModel) {
226         final Set<String> declaredFeatures = new HashSet<>();
227
228         yangModel.getYangModelRoot().getModule().getFeatures().forEach(yFeature -> declaredFeatures.add(yFeature
229                 .getFeatureName()));
230
231         /*
232          * Features can also be defined inside submodules.
233          */
234         yangModel.getYangModelRoot().getOwnedSubmodules().forEach(submoduleYangModelRoot -> {
235             submoduleYangModelRoot.getSubmodule().getFeatures().forEach(yFeature -> declaredFeatures.add(yFeature
236                     .getFeatureName()));
237         });
238
239         return declaredFeatures;
240     }
241 }