Modify config-api exceptions, bump config and netconf to 0.2.5-SNAPSHOT.
[controller.git] / opendaylight / netconf / config-netconf-connector / src / main / java / org / opendaylight / controller / netconf / confignetconfconnector / operations / editconfig / EditConfig.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.Maps;
14 import com.google.common.collect.Multimap;
15 import org.opendaylight.controller.config.api.ValidationException;
16 import org.opendaylight.controller.config.util.ConfigRegistryClient;
17 import org.opendaylight.controller.config.util.ConfigTransactionClient;
18 import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot;
19 import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
20 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
21 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity;
22 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag;
23 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType;
24 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Config;
25 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.InstanceConfig;
26 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.InstanceConfigElementResolved;
27 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.ModuleConfig;
28 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.ModuleElementDefinition;
29 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.ModuleElementResolved;
30 import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services;
31 import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation;
32 import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigXmlParser.EditConfigExecution;
33 import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider;
34 import org.opendaylight.controller.netconf.util.xml.XmlElement;
35 import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
36 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.Module;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42
43 import javax.management.InstanceNotFoundException;
44 import javax.management.ObjectName;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.Map;
48 import java.util.Map.Entry;
49 import java.util.Set;
50
51 public class EditConfig extends AbstractConfigNetconfOperation {
52
53     private static final Logger logger = LoggerFactory.getLogger(EditConfig.class);
54
55     private final YangStoreSnapshot yangStoreSnapshot;
56
57     private final TransactionProvider transactionProvider;
58     private EditConfigXmlParser editConfigXmlParser;
59
60     public EditConfig(YangStoreSnapshot yangStoreSnapshot, TransactionProvider transactionProvider,
61             ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) {
62         super(configRegistryClient, netconfSessionIdForReporting);
63         this.yangStoreSnapshot = yangStoreSnapshot;
64         this.transactionProvider = transactionProvider;
65         this.editConfigXmlParser = new EditConfigXmlParser();
66     }
67
68     @VisibleForTesting
69     Element getResponseInternal(final Document document,
70             final EditConfigXmlParser.EditConfigExecution editConfigExecution) throws NetconfDocumentedException {
71
72         if (editConfigExecution.shouldTest()) {
73             executeTests(configRegistryClient, editConfigExecution);
74         }
75
76         if (editConfigExecution.shouldSet()) {
77             executeSet(configRegistryClient, editConfigExecution);
78         }
79
80         logger.trace("Operation {} successful", EditConfigXmlParser.EDIT_CONFIG);
81
82         return document.createElement(XmlNetconfConstants.OK);
83     }
84
85     private void executeSet(ConfigRegistryClient configRegistryClient,
86             EditConfigXmlParser.EditConfigExecution editConfigExecution) throws NetconfDocumentedException {
87         try {
88             set(configRegistryClient, editConfigExecution);
89
90         } catch (IllegalStateException e) {
91             //FIXME: when can IllegalStateException be thrown?
92             // JmxAttributeValidationException is wrapped in DynamicWritableWrapper with ValidationException
93             // ValidationException is not thrown until validate or commit is issued
94             logger.warn("Set phase for {} failed", EditConfigXmlParser.EDIT_CONFIG, e);
95             final Map<String, String> errorInfo = new HashMap<>();
96             errorInfo.put(ErrorTag.operation_failed.name(), e.getMessage());
97             throw new NetconfDocumentedException("Test phase: " + e.getMessage(), e, ErrorType.application,
98                     ErrorTag.operation_failed, ErrorSeverity.error, errorInfo);
99         }
100         logger.debug("Set phase for {} operation successful", EditConfigXmlParser.EDIT_CONFIG);
101     }
102
103     private void executeTests(ConfigRegistryClient configRegistryClient,
104             EditConfigExecution editConfigExecution) throws NetconfDocumentedException {
105         try {
106             test(configRegistryClient, editConfigExecution, editConfigExecution.getDefaultStrategy());
107         } catch (IllegalStateException | ValidationException e) {
108             //FIXME: when can IllegalStateException be thrown?
109             logger.warn("Test phase for {} failed", EditConfigXmlParser.EDIT_CONFIG, e);
110             final Map<String, String> errorInfo = new HashMap<>();
111             errorInfo.put(ErrorTag.operation_failed.name(), e.getMessage());
112             throw new NetconfDocumentedException("Test phase: " + e.getMessage(), e, ErrorType.application,
113                     ErrorTag.operation_failed, ErrorSeverity.error, errorInfo);
114         }
115         logger.debug("Test phase for {} operation successful", EditConfigXmlParser.EDIT_CONFIG);
116     }
117
118     private void test(ConfigRegistryClient configRegistryClient, EditConfigExecution execution,
119             EditStrategyType editStrategyType) throws ValidationException {
120         ObjectName taON = transactionProvider.getTestTransaction();
121         try {
122
123             // default strategy = replace wipes config
124             if (editStrategyType == EditStrategyType.replace) {
125                 transactionProvider.wipeTestTransaction(taON);
126             }
127
128             ConfigTransactionClient ta = configRegistryClient.getConfigTransactionClient(taON);
129
130             handleMisssingInstancesOnTransaction(ta, execution);
131             setServicesOnTransaction(ta, execution);
132             setOnTransaction(ta, execution);
133             transactionProvider.validateTestTransaction(taON);
134         } finally {
135             transactionProvider.abortTestTransaction(taON);
136         }
137     }
138
139     private void set(ConfigRegistryClient configRegistryClient,
140             EditConfigXmlParser.EditConfigExecution editConfigExecution) {
141         ObjectName taON = transactionProvider.getOrCreateTransaction();
142
143         // default strategy = replace wipes config
144         if (editConfigExecution.getDefaultStrategy() == EditStrategyType.replace) {
145             transactionProvider.wipeTransaction();
146         }
147
148         ConfigTransactionClient ta = configRegistryClient.getConfigTransactionClient(taON);
149
150         handleMisssingInstancesOnTransaction(ta, editConfigExecution);
151         setServicesOnTransaction(ta, editConfigExecution);
152         setOnTransaction(ta, editConfigExecution);
153     }
154
155     private void setServicesOnTransaction(ConfigTransactionClient ta, EditConfigExecution execution) {
156
157         Services services = execution.getServices();
158
159         Map<String, Map<String, Map<String, Services.ServiceInstance>>> namespaceToServiceNameToRefNameToInstance = services
160                 .getNamespaceToServiceNameToRefNameToInstance();
161
162         for (String serviceNamespace : namespaceToServiceNameToRefNameToInstance.keySet()) {
163             for (String serviceName : namespaceToServiceNameToRefNameToInstance.get(serviceNamespace).keySet()) {
164
165                 String qnameOfService = getQname(ta, serviceNamespace, serviceName);
166                 Map<String, Services.ServiceInstance> refNameToInstance = namespaceToServiceNameToRefNameToInstance
167                         .get(serviceNamespace).get(serviceName);
168
169                 for (String refName : refNameToInstance.keySet()) {
170                     ObjectName on = refNameToInstance.get(refName).getObjectName(ta.getTransactionName());
171                     try {
172                         ObjectName saved = ta.saveServiceReference(qnameOfService, refName, on);
173                         logger.debug("Saving service {} with on {} under name {} with service on {}", qnameOfService,
174                                 on, refName, saved);
175                     } catch (InstanceNotFoundException e) {
176                         throw new IllegalStateException("Unable to save ref name " + refName + " for instance " + on, e);
177                     }
178                 }
179             }
180         }
181     }
182
183     private String getQname(ConfigTransactionClient ta, String namespace, String serviceName) {
184         return ta.getServiceInterfaceName(namespace, serviceName);
185     }
186
187     private void setOnTransaction(ConfigTransactionClient ta, EditConfigExecution execution) {
188
189         for (Multimap<String, ModuleElementResolved> modulesToResolved : execution.getResolvedXmlElements(ta).values()) {
190
191             for (Entry<String, ModuleElementResolved> moduleToResolved : modulesToResolved.entries()) {
192                 String moduleName = moduleToResolved.getKey();
193
194                 ModuleElementResolved moduleElementResolved = moduleToResolved.getValue();
195                 String instanceName = moduleElementResolved.getInstanceName();
196
197                 InstanceConfigElementResolved ice = moduleElementResolved.getInstanceConfigElementResolved();
198                 EditConfigStrategy strategy = ice.getEditStrategy();
199                 strategy.executeConfiguration(moduleName, instanceName, ice.getConfiguration(), ta, execution.getServiceRegistryWrapper(ta));
200             }
201         }
202     }
203
204     private void handleMisssingInstancesOnTransaction(ConfigTransactionClient ta,
205             EditConfigExecution execution) {
206
207         for (Multimap<String,ModuleElementDefinition> modulesToResolved : execution.getModulesDefinition(ta).values()) {
208             for (Entry<String, ModuleElementDefinition> moduleToResolved : modulesToResolved.entries()) {
209                 String moduleName = moduleToResolved.getKey();
210
211                 ModuleElementDefinition moduleElementDefinition = moduleToResolved.getValue();
212
213                 EditConfigStrategy strategy = moduleElementDefinition.getEditStrategy();
214                 strategy.executeConfiguration(moduleName, moduleElementDefinition.getInstanceName(), null, ta, execution.getServiceRegistryWrapper(ta));
215             }
216         }
217     }
218
219     public static Config getConfigMapping(ConfigRegistryClient configRegistryClient, YangStoreSnapshot yangStoreSnapshot) {
220         Map<String, Map<String, ModuleConfig>> factories = transformMbeToModuleConfigs(configRegistryClient,
221                 yangStoreSnapshot.getModuleMXBeanEntryMap());
222         Map<String, Map<Date, IdentityMapping>> identitiesMap = transformIdentities(yangStoreSnapshot.getModules());
223         return new Config(factories, identitiesMap);
224     }
225
226
227     public static class IdentityMapping {
228         private final Map<String, IdentitySchemaNode> identityNameToSchemaNode;
229
230         IdentityMapping() {
231             this.identityNameToSchemaNode = Maps.newHashMap();
232         }
233
234         void addIdSchemaNode(IdentitySchemaNode node) {
235             String name = node.getQName().getLocalName();
236             Preconditions.checkState(identityNameToSchemaNode.containsKey(name) == false);
237             identityNameToSchemaNode.put(name, node);
238         }
239
240         public boolean containsIdName(String idName) {
241             return identityNameToSchemaNode.containsKey(idName);
242         }
243
244         // FIXME method never used
245         public IdentitySchemaNode getIdentitySchemaNode(String idName) {
246             Preconditions.checkState(identityNameToSchemaNode.containsKey(idName), "No identity under name %s", idName);
247             return identityNameToSchemaNode.get(idName);
248         }
249     }
250
251     private static Map<String, Map<Date, IdentityMapping>> transformIdentities(Set<Module> modules) {
252         Map<String, Map<Date, IdentityMapping>> mappedIds = Maps.newHashMap();
253         for (Module module : modules) {
254             String namespace = module.getNamespace().toString();
255             Map<Date, IdentityMapping> revisionsByNamespace= mappedIds.get(namespace);
256             if(revisionsByNamespace == null) {
257                 revisionsByNamespace = Maps.newHashMap();
258                 mappedIds.put(namespace, revisionsByNamespace);
259             }
260
261             Date revision = module.getRevision();
262             Preconditions.checkState(revisionsByNamespace.containsKey(revision) == false,
263                     "Duplicate revision %s for namespace %s", revision, namespace);
264
265             IdentityMapping identityMapping = revisionsByNamespace.get(revision);
266             if(identityMapping == null) {
267                 identityMapping = new IdentityMapping();
268                 revisionsByNamespace.put(revision, identityMapping);
269             }
270
271             for (IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
272                 identityMapping.addIdSchemaNode(identitySchemaNode);
273             }
274
275         }
276
277         return mappedIds;
278     }
279
280     public static Map<String/* Namespace from yang file */,
281             Map<String /* Name of module entry from yang file */, ModuleConfig>> transformMbeToModuleConfigs
282             (final ConfigRegistryClient configRegistryClient, Map<String/* Namespace from yang file */,
283                     Map<String /* Name of module entry from yang file */, ModuleMXBeanEntry>> mBeanEntries) {
284
285         Map<String, Map<String, ModuleConfig>> namespaceToModuleNameToModuleConfig = Maps.newHashMap();
286
287         for (String namespace : mBeanEntries.keySet()) {
288             for (Entry<String, ModuleMXBeanEntry> moduleNameToMbe : mBeanEntries.get(namespace).entrySet()) {
289                 String moduleName = moduleNameToMbe.getKey();
290                 ModuleMXBeanEntry moduleMXBeanEntry = moduleNameToMbe.getValue();
291
292                 ModuleConfig moduleConfig = new ModuleConfig(moduleName, new InstanceConfig(configRegistryClient,
293                         moduleMXBeanEntry.getAttributes()), moduleMXBeanEntry
294                         .getProvidedServices().values());
295
296                 Map<String, ModuleConfig> moduleNameToModuleConfig = namespaceToModuleNameToModuleConfig.get(namespace);
297                 if(moduleNameToModuleConfig == null) {
298                     moduleNameToModuleConfig = Maps.newHashMap();
299                     namespaceToModuleNameToModuleConfig.put(namespace, moduleNameToModuleConfig);
300                 }
301
302                 moduleNameToModuleConfig.put(moduleName, moduleConfig);
303             }
304         }
305
306         return namespaceToModuleNameToModuleConfig;
307     }
308
309     @Override
310     protected String getOperationName() {
311         return EditConfigXmlParser.EDIT_CONFIG;
312     }
313
314     @Override
315     protected Element handle(Document document, XmlElement xml) throws NetconfDocumentedException {
316
317         EditConfigXmlParser.EditConfigExecution editConfigExecution;
318         Config cfg = getConfigMapping(configRegistryClient, yangStoreSnapshot);
319         try {
320             editConfigExecution = editConfigXmlParser.fromXml(xml, cfg, transactionProvider, configRegistryClient);
321         } catch (IllegalStateException e) {
322             logger.warn("Error parsing xml", e);
323             final Map<String, String> errorInfo = new HashMap<>();
324             errorInfo.put(ErrorTag.missing_attribute.name(), "Error parsing xml: " + e.getMessage());
325             throw new NetconfDocumentedException(e.getMessage(), ErrorType.rpc, ErrorTag.missing_attribute,
326                     ErrorSeverity.error, errorInfo);
327         } catch (final IllegalArgumentException e) {
328             logger.warn("Error parsing xml", e);
329             final Map<String, String> errorInfo = new HashMap<>();
330             errorInfo.put(ErrorTag.bad_attribute.name(), e.getMessage());
331             throw new NetconfDocumentedException(e.getMessage(), ErrorType.rpc, ErrorTag.bad_attribute,
332                     ErrorSeverity.error, errorInfo);
333         } catch (final UnsupportedOperationException e) {
334             logger.warn("Unsupported", e);
335             final Map<String, String> errorInfo = new HashMap<>();
336             errorInfo.put(ErrorTag.operation_not_supported.name(), "Unsupported option for 'edit-config'");
337             throw new NetconfDocumentedException(e.getMessage(), ErrorType.application,
338                     ErrorTag.operation_not_supported, ErrorSeverity.error, errorInfo);
339         }
340
341         return getResponseInternal(document, editConfigExecution);
342     }
343
344 }