NETCONF-557: Add support for URL capability
[netconf.git] / netconf / netconf-impl / src / main / java / org / opendaylight / netconf / impl / osgi / NetconfCapabilityMonitoringService.java
1 /*
2  * Copyright (c) 2016 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 package org.opendaylight.netconf.impl.osgi;
9
10 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_CANDIDATE_1_0;
11 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_URL_1_0;
12
13 import com.google.common.base.Function;
14 import com.google.common.base.Optional;
15 import com.google.common.base.Preconditions;
16 import com.google.common.collect.Collections2;
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.ImmutableList.Builder;
19 import com.google.common.collect.Lists;
20 import com.google.common.collect.Maps;
21 import com.google.common.collect.Sets;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import org.opendaylight.netconf.api.capability.BasicCapability;
30 import org.opendaylight.netconf.api.capability.Capability;
31 import org.opendaylight.netconf.api.monitoring.CapabilityListener;
32 import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
33 import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
34 import org.opendaylight.netconf.notifications.BaseNotificationPublisherRegistration;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.CapabilitiesBuilder;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.SchemasBuilder;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.SchemaBuilder;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.SchemaKey;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChangeBuilder;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.changed.by.parms.ChangedByBuilder;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.changed.by.parms.changed.by.server.or.user.ServerBuilder;
48
49 class NetconfCapabilityMonitoringService implements CapabilityListener, AutoCloseable {
50
51     private static final Schema.Location NETCONF_LOCATION = new Schema.Location(Schema.Location.Enumeration.NETCONF);
52     private static final List<Schema.Location> NETCONF_LOCATIONS = ImmutableList.of(NETCONF_LOCATION);
53     private static final BasicCapability CANDIDATE_CAPABILITY =
54             new BasicCapability(URN_IETF_PARAMS_NETCONF_CAPABILITY_CANDIDATE_1_0);
55     private static final BasicCapability URL_CAPABILITY =
56             new BasicCapability(URN_IETF_PARAMS_NETCONF_CAPABILITY_URL_1_0);
57     private static final Function<Capability, Uri> CAPABILITY_TO_URI = input -> new Uri(input.getCapabilityUri());
58
59     private final NetconfOperationServiceFactory netconfOperationProvider;
60     private final Map<Uri, Capability> capabilities = Maps.newHashMap();
61     private final Map<String, Map<String, String>> mappedModulesToRevisionToSchema = Maps.newHashMap();
62
63
64     private final Set<NetconfMonitoringService.CapabilitiesListener> listeners = Sets.newHashSet();
65     private volatile BaseNotificationPublisherRegistration notificationPublisher;
66
67     NetconfCapabilityMonitoringService(final NetconfOperationServiceFactory netconfOperationProvider) {
68         this.netconfOperationProvider = netconfOperationProvider;
69         netconfOperationProvider.registerCapabilityListener(this);
70     }
71
72     @SuppressWarnings("checkstyle:IllegalCatch")
73     synchronized Schemas getSchemas() {
74         try {
75             return transformSchemas(netconfOperationProvider.getCapabilities());
76         } catch (final RuntimeException e) {
77             throw e;
78         } catch (final Exception e) {
79             throw new IllegalStateException("Exception while closing", e);
80         }
81     }
82
83     synchronized String getSchemaForModuleRevision(final String moduleName, final Optional<String> revision) {
84
85         Map<String, String> revisionMapRequest = mappedModulesToRevisionToSchema.get(moduleName);
86         Preconditions.checkState(revisionMapRequest != null,
87                 "Capability for module %s not present, available modules : %s",
88                 moduleName, Collections2.transform(capabilities.values(), CAPABILITY_TO_URI));
89
90         if (revision.isPresent()) {
91             String schema = revisionMapRequest.get(revision.get());
92
93             Preconditions.checkState(schema != null,
94                     "Capability for module %s:%s not present, available revisions for module: %s", moduleName,
95                     revision.get(), revisionMapRequest.keySet());
96
97             return schema;
98         }
99
100         Preconditions.checkState(revisionMapRequest.size() == 1,
101                 "Expected 1 capability for module %s, available revisions : %s", moduleName,
102                 revisionMapRequest.keySet());
103         //Only one revision is present, so return it
104         return revisionMapRequest.values().iterator().next();
105     }
106
107     private void updateCapabilityToSchemaMap(final Set<Capability> added, final Set<Capability> removed) {
108         for (final Capability cap : added) {
109             if (!isValidModuleCapability(cap)) {
110                 continue;
111             }
112
113             final String currentModuleName = cap.getModuleName().get();
114             Map<String, String> revisionMap =
115                 mappedModulesToRevisionToSchema.computeIfAbsent(currentModuleName, k -> Maps.newHashMap());
116
117             final String currentRevision = cap.getRevision().get();
118             revisionMap.put(currentRevision, cap.getCapabilitySchema().get());
119         }
120         for (final Capability cap : removed) {
121             if (!isValidModuleCapability(cap)) {
122                 continue;
123             }
124             final Map<String, String> revisionMap = mappedModulesToRevisionToSchema.get(cap.getModuleName().get());
125             if (revisionMap != null) {
126                 revisionMap.remove(cap.getRevision().get());
127                 if (revisionMap.isEmpty()) {
128                     mappedModulesToRevisionToSchema.remove(cap.getModuleName().get());
129                 }
130             }
131         }
132     }
133
134     private static boolean isValidModuleCapability(final Capability cap) {
135         return cap.getModuleName().isPresent()
136                 && cap.getRevision().isPresent()
137                 && cap.getCapabilitySchema().isPresent();
138     }
139
140
141     synchronized Capabilities getCapabilities() {
142         return new CapabilitiesBuilder().setCapability(Lists.newArrayList(capabilities.keySet())).build();
143     }
144
145     synchronized AutoCloseable registerListener(final NetconfMonitoringService.CapabilitiesListener listener) {
146         listeners.add(listener);
147         listener.onCapabilitiesChanged(getCapabilities());
148         listener.onSchemasChanged(getSchemas());
149         return () -> {
150             synchronized (NetconfCapabilityMonitoringService.this) {
151                 listeners.remove(listener);
152             }
153         };
154     }
155
156     private static Schemas transformSchemas(final Set<Capability> caps) {
157         final List<Schema> schemas = new ArrayList<>(caps.size());
158         for (final Capability cap : caps) {
159             if (cap.getCapabilitySchema().isPresent()) {
160                 final SchemaBuilder builder = new SchemaBuilder();
161
162                 Preconditions.checkState(isValidModuleCapability(cap));
163
164                 builder.setNamespace(new Uri(cap.getModuleNamespace().get()));
165
166                 final String version = cap.getRevision().get();
167                 builder.setVersion(version);
168
169                 final String identifier = cap.getModuleName().get();
170                 builder.setIdentifier(identifier);
171
172                 builder.setFormat(Yang.class);
173
174                 builder.setLocation(transformLocations(cap.getLocation()));
175
176                 builder.withKey(new SchemaKey(Yang.class, identifier, version));
177
178                 schemas.add(builder.build());
179             }
180         }
181
182         return new SchemasBuilder().setSchema(schemas).build();
183     }
184
185     private static List<Schema.Location> transformLocations(final Collection<String> locations) {
186         if (locations.isEmpty()) {
187             return NETCONF_LOCATIONS;
188         }
189
190         final Builder<Schema.Location> b = ImmutableList.builder();
191         b.add(NETCONF_LOCATION);
192
193         for (final String location : locations) {
194             b.add(new Schema.Location(new Uri(location)));
195         }
196
197         return b.build();
198     }
199
200     private static Set<Capability> setupCapabilities(final Set<Capability> caps) {
201         Set<Capability> capabilities = new HashSet<>(caps);
202         capabilities.add(CANDIDATE_CAPABILITY);
203         capabilities.add(URL_CAPABILITY);
204         // TODO rollback on error not supported EditConfigXmlParser:100
205         // [RFC6241] 8.5.  Rollback-on-Error Capability
206         // capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:rollback-on-error:1.0"));
207         return capabilities;
208     }
209
210     @Override
211     public synchronized void close() {
212         listeners.clear();
213         capabilities.clear();
214     }
215
216     @Override
217     public synchronized void onCapabilitiesChanged(final Set<Capability> added, final Set<Capability> removed) {
218         onCapabilitiesAdded(added);
219         onCapabilitiesRemoved(removed);
220         updateCapabilityToSchemaMap(added, removed);
221         notifyCapabilityChanged(getCapabilities());
222
223         // publish notification to notification collector about changed capabilities
224         if (notificationPublisher != null) {
225             notificationPublisher.onCapabilityChanged(computeDiff(added, removed));
226         }
227     }
228
229     private void notifyCapabilityChanged(final Capabilities newCapabilities) {
230         for (NetconfMonitoringService.CapabilitiesListener listener : listeners) {
231             listener.onCapabilitiesChanged(newCapabilities);
232             listener.onSchemasChanged(getSchemas());
233         }
234     }
235
236
237     private static NetconfCapabilityChange computeDiff(final Set<Capability> added, final Set<Capability> removed) {
238         final NetconfCapabilityChangeBuilder netconfCapabilityChangeBuilder = new NetconfCapabilityChangeBuilder();
239         netconfCapabilityChangeBuilder
240                 .setChangedBy(new ChangedByBuilder().setServerOrUser(
241                     new ServerBuilder().setServer(Boolean.TRUE).build()).build());
242         netconfCapabilityChangeBuilder.setDeletedCapability(Lists.newArrayList(Collections2
243                 .transform(removed, CAPABILITY_TO_URI)));
244         netconfCapabilityChangeBuilder.setAddedCapability(Lists.newArrayList(Collections2
245                 .transform(added, CAPABILITY_TO_URI)));
246         // TODO modified should be computed ... but why ?
247         netconfCapabilityChangeBuilder.setModifiedCapability(Collections.emptyList());
248         return netconfCapabilityChangeBuilder.build();
249     }
250
251
252     private void onCapabilitiesAdded(final Set<Capability> addedCaps) {
253         this.capabilities.putAll(Maps.uniqueIndex(setupCapabilities(addedCaps), CAPABILITY_TO_URI));
254     }
255
256     private void onCapabilitiesRemoved(final Set<Capability> removedCaps) {
257         for (final Capability addedCap : removedCaps) {
258             capabilities.remove(new Uri(addedCap.getCapabilityUri()));
259         }
260     }
261
262     void setNotificationPublisher(final BaseNotificationPublisherRegistration notificationPublisher) {
263         this.notificationPublisher = notificationPublisher;
264     }
265 }