2b4e63364671bf987ddf3287679525e757be4908
[controller.git] / opendaylight / config / config-manager-facade-xml / src / main / java / org / opendaylight / controller / config / facade / xml / ConfigSubsystemFacade.java
1 /*
2  * Copyright (c) 2015 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.config.facade.xml;
10
11 import com.google.common.base.Optional;
12 import com.google.common.collect.Multimap;
13 import java.io.Closeable;
14 import java.util.Date;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import javax.management.InstanceNotFoundException;
20 import javax.management.ObjectName;
21 import org.opendaylight.controller.config.api.ConflictingVersionException;
22 import org.opendaylight.controller.config.api.ValidationException;
23 import org.opendaylight.controller.config.api.jmx.CommitStatus;
24 import org.opendaylight.controller.config.facade.xml.mapping.IdentityMapping;
25 import org.opendaylight.controller.config.facade.xml.mapping.config.Config;
26 import org.opendaylight.controller.config.facade.xml.mapping.config.InstanceConfig;
27 import org.opendaylight.controller.config.facade.xml.mapping.config.InstanceConfigElementResolved;
28 import org.opendaylight.controller.config.facade.xml.mapping.config.ModuleConfig;
29 import org.opendaylight.controller.config.facade.xml.mapping.config.ModuleElementDefinition;
30 import org.opendaylight.controller.config.facade.xml.mapping.config.ModuleElementResolved;
31 import org.opendaylight.controller.config.facade.xml.mapping.config.ServiceRegistryWrapper;
32 import org.opendaylight.controller.config.facade.xml.mapping.config.Services;
33 import org.opendaylight.controller.config.facade.xml.osgi.YangStoreContext;
34 import org.opendaylight.controller.config.facade.xml.osgi.YangStoreService;
35 import org.opendaylight.controller.config.facade.xml.runtime.InstanceRuntime;
36 import org.opendaylight.controller.config.facade.xml.runtime.ModuleRuntime;
37 import org.opendaylight.controller.config.facade.xml.runtime.Runtime;
38 import org.opendaylight.controller.config.facade.xml.strategy.EditConfigStrategy;
39 import org.opendaylight.controller.config.facade.xml.strategy.EditStrategyType;
40 import org.opendaylight.controller.config.facade.xml.transactions.TransactionProvider;
41 import org.opendaylight.controller.config.util.BeanReader;
42 import org.opendaylight.controller.config.util.ConfigRegistryClient;
43 import org.opendaylight.controller.config.util.ConfigTransactionClient;
44 import org.opendaylight.controller.config.util.xml.DocumentedException;
45 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
46 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
47 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
48 import org.opendaylight.controller.config.util.xml.XmlElement;
49 import org.opendaylight.controller.config.util.xml.XmlMappingConstants;
50 import org.opendaylight.controller.config.util.xml.XmlUtil;
51 import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
52 import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry;
53 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.Module;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57 import org.w3c.dom.Document;
58 import org.w3c.dom.Element;
59
60 /**
61  * Config subsystem facade for xml format
62  * <p>
63  * TODO extract generic interface for config subsystem facades
64  */
65 public class ConfigSubsystemFacade implements Closeable {
66
67     private static final Logger LOG = LoggerFactory.getLogger(ConfigSubsystemFacade.class);
68     private final YangStoreService yangStoreService;
69     private final TransactionProvider transactionProvider;
70     private final ConfigRegistryClient configRegistryClient;
71     private final ConfigRegistryClient configRegistryClientNoNotifications;
72     private final RpcFacade rpcFacade;
73
74     public ConfigSubsystemFacade(final ConfigRegistryClient configRegistryClient, final ConfigRegistryClient configRegistryClientNoNotifications, final YangStoreService yangStoreService, final String id) {
75         this.configRegistryClient = configRegistryClient;
76         this.configRegistryClientNoNotifications = configRegistryClientNoNotifications;
77         this.yangStoreService = yangStoreService;
78         this.transactionProvider = new TransactionProvider(configRegistryClient, id);
79         rpcFacade = new RpcFacade(yangStoreService, configRegistryClient);
80     }
81
82     public ConfigSubsystemFacade(final ConfigRegistryClient configRegistryClient, final ConfigRegistryClient configRegistryClientNoNotifications, final YangStoreService yangStoreService, final TransactionProvider txProvider) {
83         this.configRegistryClient = configRegistryClient;
84         this.configRegistryClientNoNotifications = configRegistryClientNoNotifications;
85         this.yangStoreService = yangStoreService;
86         this.transactionProvider = txProvider;
87         rpcFacade = new RpcFacade(yangStoreService, configRegistryClient);
88     }
89
90     public Element getConfiguration(final Document document, final Datastore source, final Optional<String> maybeNamespace) {
91
92         final ConfigTransactionClient registryClient;
93         // Read current state from a transaction, if running is source, then start new transaction just for reading
94         // in case of candidate, get current transaction representing candidate
95         if (source == Datastore.running) {
96             final ObjectName readTx = transactionProvider.getOrCreateReadTransaction();
97             registryClient = configRegistryClient.getConfigTransactionClient(readTx);
98         } else {
99             registryClient = configRegistryClient.getConfigTransactionClient(transactionProvider.getOrCreateTransaction());
100         }
101
102         try {
103             Element dataElement = XmlUtil.createElement(document, XmlMappingConstants.DATA_KEY, Optional.<String>absent());
104             final Set<ObjectName> instances = Datastore.getInstanceQueryStrategy(source, this.transactionProvider)
105                     .queryInstances(configRegistryClient);
106
107             final Config configMapping =
108                     new Config(transformMbeToModuleConfigs(yangStoreService.getModuleMXBeanEntryMap()), yangStoreService.getEnumResolver());
109
110             ServiceRegistryWrapper serviceTracker = new ServiceRegistryWrapper(registryClient);
111             dataElement = configMapping.toXml(instances, maybeNamespace, document, dataElement, serviceTracker);
112
113             return dataElement;
114         } finally {
115             if (source == Datastore.running) {
116                 transactionProvider.closeReadTransaction();
117             }
118         }
119     }
120
121     public void executeConfigExecution(final ConfigExecution configExecution) throws DocumentedException, ValidationException {
122         if (configExecution.shouldTest()) {
123             executeTests(configExecution);
124         }
125
126         if (configExecution.shouldSet()) {
127             executeSet(configExecution);
128         }
129     }
130
131     public CommitStatus commitTransaction() throws DocumentedException, ValidationException, ConflictingVersionException {
132         final CommitStatus status = this.transactionProvider.commitTransaction();
133         LOG.trace("Transaction committed successfully: {}", status);
134         return status;
135     }
136
137     public CommitStatus commitSilentTransaction() throws DocumentedException, ValidationException, ConflictingVersionException {
138         final CommitStatus status = this.transactionProvider.commitTransaction(configRegistryClientNoNotifications);
139         LOG.trace("Transaction committed successfully: {}", status);
140         return status;
141     }
142
143     private void executeSet(final ConfigExecution configExecution) throws DocumentedException {
144         set(configExecution);
145         LOG.debug("Set phase for {} operation successful, element: ", configExecution.getDefaultStrategy(), configExecution.getConfigElement());
146     }
147
148     private void executeTests(final ConfigExecution configExecution) throws DocumentedException, ValidationException {
149         test(configExecution, configExecution.getDefaultStrategy());
150         LOG.debug("Test phase for {} operation successful, element: ", configExecution.getDefaultStrategy(), configExecution.getConfigElement());
151     }
152
153     private void test(final ConfigExecution execution, final EditStrategyType editStrategyType) throws ValidationException, DocumentedException {
154         ObjectName taON = transactionProvider.getTestTransaction();
155         try {
156             // default strategy = replace wipes config
157             if (editStrategyType == EditStrategyType.replace) {
158                 transactionProvider.wipeTestTransaction(taON);
159             }
160
161             ConfigTransactionClient ta = configRegistryClient.getConfigTransactionClient(taON);
162
163             handleMisssingInstancesOnTransaction(ta, execution);
164             setServicesOnTransaction(ta, execution);
165             setOnTransaction(ta, execution);
166             transactionProvider.validateTestTransaction(taON);
167         } finally {
168             transactionProvider.abortTestTransaction(taON);
169         }
170     }
171
172     private void set(final ConfigExecution ConfigExecution) throws DocumentedException {
173         ObjectName taON = transactionProvider.getOrCreateTransaction();
174
175         // default strategy = replace wipes config
176         if (ConfigExecution.getDefaultStrategy() == EditStrategyType.replace) {
177             transactionProvider.wipeTransaction();
178         }
179
180         ConfigTransactionClient ta = configRegistryClient.getConfigTransactionClient(taON);
181
182         handleMisssingInstancesOnTransaction(ta, ConfigExecution);
183         setServicesOnTransaction(ta, ConfigExecution);
184         setOnTransaction(ta, ConfigExecution);
185     }
186
187     private void setServicesOnTransaction(final ConfigTransactionClient ta, final ConfigExecution execution) throws DocumentedException {
188
189         Services services = execution.getServices();
190
191         Map<String, Map<String, Map<String, Services.ServiceInstance>>> namespaceToServiceNameToRefNameToInstance = services
192                 .getNamespaceToServiceNameToRefNameToInstance();
193
194         for (Map.Entry<String, Map<String, Map<String, Services.ServiceInstance>>> namespaceToServiceToRefEntry : namespaceToServiceNameToRefNameToInstance.entrySet()) {
195             for (Map.Entry<String, Map<String, Services.ServiceInstance>> serviceToRefEntry : namespaceToServiceToRefEntry.getValue().entrySet()) {
196
197                 String qnameOfService = getQname(ta, namespaceToServiceToRefEntry.getKey(), serviceToRefEntry.getKey());
198                 Map<String, Services.ServiceInstance> refNameToInstance = serviceToRefEntry.getValue();
199
200                 for (Map.Entry<String, Services.ServiceInstance> refNameToServiceEntry : refNameToInstance.entrySet()) {
201                     ObjectName on = refNameToServiceEntry.getValue().getObjectName(ta.getTransactionName());
202                     try {
203                         if (Services.ServiceInstance.EMPTY_SERVICE_INSTANCE == refNameToServiceEntry.getValue()) {
204                             ta.removeServiceReference(qnameOfService, refNameToServiceEntry.getKey());
205                             LOG.debug("Removing service {} with name {}", qnameOfService, refNameToServiceEntry.getKey());
206                         } else {
207                             ObjectName saved = ta.saveServiceReference(qnameOfService, refNameToServiceEntry.getKey(), on);
208                             LOG.debug("Saving service {} with on {} under name {} with service on {}", qnameOfService,
209                                     on, refNameToServiceEntry.getKey(), saved);
210                         }
211                     } catch (final InstanceNotFoundException e) {
212                         throw new DocumentedException(String.format("Unable to edit ref name " + refNameToServiceEntry.getKey() + " for instance " + on, e),
213                                 ErrorType.APPLICATION,
214                                 ErrorTag.OPERATION_FAILED,
215                                 ErrorSeverity.ERROR);
216                     }
217                 }
218             }
219         }
220     }
221
222     private String getQname(final ConfigTransactionClient ta, final String namespace, final String serviceName) {
223         return ta.getServiceInterfaceName(namespace, serviceName);
224     }
225
226     private void setOnTransaction(final ConfigTransactionClient ta, final ConfigExecution execution) throws DocumentedException {
227
228         for (Multimap<String, ModuleElementResolved> modulesToResolved : execution.getResolvedXmlElements(ta).values()) {
229
230             for (Map.Entry<String, ModuleElementResolved> moduleToResolved : modulesToResolved.entries()) {
231                 String moduleName = moduleToResolved.getKey();
232
233                 ModuleElementResolved moduleElementResolved = moduleToResolved.getValue();
234                 String instanceName = moduleElementResolved.getInstanceName();
235
236                 InstanceConfigElementResolved ice = moduleElementResolved.getInstanceConfigElementResolved();
237                 EditConfigStrategy strategy = ice.getEditStrategy();
238                 strategy.executeConfiguration(moduleName, instanceName, ice.getConfiguration(), ta, execution.getServiceRegistryWrapper(ta));
239             }
240         }
241     }
242
243     private void handleMisssingInstancesOnTransaction(final ConfigTransactionClient ta,
244                                                       final ConfigExecution execution) throws DocumentedException {
245
246         for (Multimap<String, ModuleElementDefinition> modulesToResolved : execution.getModulesDefinition(ta).values()) {
247             for (Map.Entry<String, ModuleElementDefinition> moduleToResolved : modulesToResolved.entries()) {
248                 String moduleName = moduleToResolved.getKey();
249
250                 ModuleElementDefinition moduleElementDefinition = moduleToResolved.getValue();
251
252                 EditConfigStrategy strategy = moduleElementDefinition.getEditStrategy();
253                 strategy.executeConfiguration(moduleName, moduleElementDefinition.getInstanceName(), null, ta, execution.getServiceRegistryWrapper(ta));
254             }
255         }
256     }
257
258     public Config getConfigMapping() {
259         final YangStoreContext snapshot = yangStoreService.getCurrentSnapshot();
260         Map<String, Map<String, ModuleConfig>> factories = transformMbeToModuleConfigs(snapshot.getModuleMXBeanEntryMap());
261         Map<String, Map<Date, IdentityMapping>> identitiesMap = transformIdentities(snapshot.getModules());
262         return new Config(factories, identitiesMap, snapshot.getEnumResolver());
263     }
264
265     private static Map<String, Map<Date, IdentityMapping>> transformIdentities(final Set<Module> modules) {
266         Map<String, Map<Date, IdentityMapping>> mappedIds = new HashMap<>();
267         for (Module module : modules) {
268             String namespace = module.getNamespace().toString();
269             Map<Date, IdentityMapping> revisionsByNamespace =
270                     mappedIds.computeIfAbsent(namespace, k -> new HashMap<>());
271
272             Date revision = module.getRevision();
273
274             IdentityMapping identityMapping =
275                     revisionsByNamespace.computeIfAbsent(revision, k -> new IdentityMapping());
276
277             for (IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
278                 identityMapping.addIdSchemaNode(identitySchemaNode);
279             }
280
281         }
282
283         return mappedIds;
284     }
285
286     public Map<String/* Namespace from yang file */,
287             Map<String /* Name of module entry from yang file */, ModuleConfig>> transformMbeToModuleConfigs(
288             final Map<String/* Namespace from yang file */,
289                     Map<String /* Name of module entry from yang file */, ModuleMXBeanEntry>> mBeanEntries) {
290         return transformMbeToModuleConfigs(configRegistryClient, mBeanEntries);
291     }
292
293     public Map<String/* Namespace from yang file */,
294             Map<String /* Name of module entry from yang file */, ModuleConfig>> transformMbeToModuleConfigs(
295             final BeanReader reader,
296                                                                                                              final Map<String/* Namespace from yang file */,
297                                                                                                                      Map<String /* Name of module entry from yang file */, ModuleMXBeanEntry>> mBeanEntries) {
298
299         Map<String, Map<String, ModuleConfig>> namespaceToModuleNameToModuleConfig = new HashMap<>();
300
301         for (Map.Entry<String, Map<String, ModuleMXBeanEntry>> namespaceToModuleToMbe : mBeanEntries.entrySet()) {
302             for (Map.Entry<String, ModuleMXBeanEntry> moduleNameToMbe : namespaceToModuleToMbe.getValue().entrySet()) {
303                 String moduleName = moduleNameToMbe.getKey();
304                 ModuleMXBeanEntry moduleMXBeanEntry = moduleNameToMbe.getValue();
305
306                 ModuleConfig moduleConfig = new ModuleConfig(moduleName,
307                         new InstanceConfig(reader, moduleMXBeanEntry.getAttributes(), moduleMXBeanEntry.getNullableDummyContainerName()));
308
309                 Map<String, ModuleConfig> moduleNameToModuleConfig =
310                         namespaceToModuleNameToModuleConfig.computeIfAbsent(namespaceToModuleToMbe.getKey(),
311                                 k -> new HashMap<>());
312
313                 moduleNameToModuleConfig.put(moduleName, moduleConfig);
314             }
315         }
316
317         return namespaceToModuleNameToModuleConfig;
318     }
319
320     public ConfigExecution getConfigExecution(final Config configMapping, final Element xmlToBePersisted) throws DocumentedException {
321         return new ConfigExecution(configMapping, XmlElement.fromDomElement(xmlToBePersisted), TestOption.testThenSet, EditStrategyType.getDefaultStrategy());
322     }
323
324     private Map<String, Map<String, ModuleRuntime>> createModuleRuntimes(final ConfigRegistryClient configRegistryClient,
325                                                                          final Map<String, Map<String, ModuleMXBeanEntry>> mBeanEntries) {
326         Map<String, Map<String, ModuleRuntime>> retVal = new HashMap<>();
327
328         for (Map.Entry<String, Map<String, ModuleMXBeanEntry>> namespaceToModuleEntry : mBeanEntries.entrySet()) {
329
330             Map<String, ModuleRuntime> innerMap = new HashMap<>();
331             Map<String, ModuleMXBeanEntry> entriesFromNamespace = namespaceToModuleEntry.getValue();
332             for (Map.Entry<String, ModuleMXBeanEntry> moduleToMXEntry : entriesFromNamespace.entrySet()) {
333
334                 ModuleMXBeanEntry mbe = moduleToMXEntry.getValue();
335
336                 Map<RuntimeBeanEntry, InstanceConfig> cache = new HashMap<>();
337                 RuntimeBeanEntry root = null;
338                 for (RuntimeBeanEntry rbe : mbe.getRuntimeBeans()) {
339                     cache.put(rbe, new InstanceConfig(configRegistryClient, rbe.getYangPropertiesToTypesMap(), mbe.getNullableDummyContainerName()));
340                     if (rbe.isRoot()) {
341                         root = rbe;
342                     }
343                 }
344
345                 if (root == null) {
346                     continue;
347                 }
348
349                 InstanceRuntime rootInstanceRuntime = createInstanceRuntime(root, cache);
350                 ModuleRuntime moduleRuntime = new ModuleRuntime(rootInstanceRuntime);
351                 innerMap.put(moduleToMXEntry.getKey(), moduleRuntime);
352             }
353
354             retVal.put(namespaceToModuleEntry.getKey(), innerMap);
355         }
356         return retVal;
357     }
358
359     private InstanceRuntime createInstanceRuntime(final RuntimeBeanEntry root, final Map<RuntimeBeanEntry, InstanceConfig> cache) {
360         Map<String, InstanceRuntime> children = new HashMap<>();
361         for (RuntimeBeanEntry child : root.getChildren()) {
362             children.put(child.getJavaNamePrefix(), createInstanceRuntime(child, cache));
363         }
364
365         return new InstanceRuntime(cache.get(root), children, createJmxToYangMap(root.getChildren()));
366     }
367
368     private Map<String, String> createJmxToYangMap(final List<RuntimeBeanEntry> children) {
369         Map<String, String> jmxToYangNamesForChildRbe = new HashMap<>();
370         for (RuntimeBeanEntry rbe : children) {
371             jmxToYangNamesForChildRbe.put(rbe.getJavaNamePrefix(), rbe.getYangName());
372         }
373         return jmxToYangNamesForChildRbe;
374     }
375
376     public Element get(final Document document) throws DocumentedException {
377         final ObjectName testTransaction = transactionProvider.getOrCreateReadTransaction();
378         final ConfigTransactionClient txClient = configRegistryClient.getConfigTransactionClient(testTransaction);
379
380         try {
381             // Runtime beans are not parts of transactions and have to be queried against the central registry
382             final Set<ObjectName> runtimeBeans = configRegistryClient.lookupRuntimeBeans();
383
384             final Set<ObjectName> configBeans = Datastore.getInstanceQueryStrategy(Datastore.running, transactionProvider)
385                     .queryInstances(configRegistryClient);
386
387             final Map<String, Map<String, ModuleRuntime>> moduleRuntimes = createModuleRuntimes(configRegistryClient,
388                     yangStoreService.getModuleMXBeanEntryMap());
389
390             final YangStoreContext yangStoreSnapshot = yangStoreService.getCurrentSnapshot();
391             final Map<String, Map<String, ModuleConfig>> moduleConfigs = transformMbeToModuleConfigs(txClient,
392                     yangStoreSnapshot.getModuleMXBeanEntryMap());
393
394             final org.opendaylight.controller.config.facade.xml.runtime.Runtime runtime = new Runtime(moduleRuntimes, moduleConfigs);
395
396             return runtime.toXml(runtimeBeans, configBeans, document, yangStoreSnapshot.getEnumResolver());
397         } finally {
398             transactionProvider.closeReadTransaction();
399         }
400     }
401
402     public void abortConfiguration() {
403         if (transactionProvider.getTransaction().isPresent()) {
404             this.transactionProvider.abortTransaction();
405         }
406     }
407
408     public void validateConfiguration() throws ValidationException {
409         transactionProvider.validateTransaction();
410     }
411
412     @Override
413     public void close() {
414         transactionProvider.close();
415     }
416
417     public RpcFacade getRpcFacade() {
418         return rpcFacade;
419     }
420
421 }
422