From: Tony Tkacik Date: Tue, 10 Feb 2015 21:06:39 +0000 (+0000) Subject: Merge "BUG-2599 Netconf notification manager initial API and Impl" X-Git-Tag: release/lithium~598 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=9b43c3ce7c46eaae9060f3ed18e6c39f76ede2e5;hp=05e04699813e6989cdf975cba027d6ff7ec44a35 Merge "BUG-2599 Netconf notification manager initial API and Impl" --- diff --git a/features/netconf/src/main/resources/features.xml b/features/netconf/src/main/resources/features.xml index 9de15630c3..2affa27d19 100644 --- a/features/netconf/src/main/resources/features.xml +++ b/features/netconf/src/main/resources/features.xml @@ -22,6 +22,8 @@ mvn:org.opendaylight.controller/netconf-api/${project.version} mvn:org.opendaylight.controller/netconf-auth/${project.version} mvn:org.opendaylight.controller/ietf-netconf-monitoring/${project.version} + mvn:org.opendaylight.controller/ietf-netconf/${project.version} + mvn:org.opendaylight.controller/ietf-netconf-notifications/${project.version} mvn:org.opendaylight.controller/ietf-netconf-monitoring-extension/${project.version} mvn:org.opendaylight.yangtools.model/ietf-inet-types/${ietf-inet-types.version} mvn:org.opendaylight.yangtools.model/ietf-yang-types/${ietf-yang-types.version} @@ -43,6 +45,7 @@ odl-config-netconf-connector odl-netconf-monitoring + odl-netconf-notifications-impl mvn:org.opendaylight.controller/netconf-impl/${project.version} @@ -50,6 +53,7 @@ odl-netconf-api odl-netconf-mapping-api odl-netconf-util + odl-netconf-notifications-api mvn:org.opendaylight.controller/config-netconf-connector/${project.version} @@ -75,5 +79,13 @@ odl-netconf-util mvn:org.opendaylight.controller/netconf-monitoring/${project.version} + + odl-netconf-api + mvn:org.opendaylight.controller/netconf-notifications-api/${project.version} + + + odl-netconf-notifications-api + mvn:org.opendaylight.controller/netconf-notifications-impl/${project.version} + diff --git a/opendaylight/netconf/netconf-artifacts/pom.xml b/opendaylight/netconf/netconf-artifacts/pom.xml index eb3cac18df..3487aa7be3 100644 --- a/opendaylight/netconf/netconf-artifacts/pom.xml +++ b/opendaylight/netconf/netconf-artifacts/pom.xml @@ -108,6 +108,12 @@ ${project.version} + + ${project.groupId} + ietf-netconf + ${project.version} + + ${project.groupId} ietf-netconf-monitoring @@ -119,6 +125,22 @@ ${project.version} + + ${project.groupId} + ietf-netconf-notifications + ${project.version} + + + ${project.groupId} + netconf-notifications-api + ${project.version} + + + ${project.groupId} + netconf-notifications-impl + ${project.version} + + ${project.groupId} netconf-client diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java new file mode 100644 index 0000000000..88c77c6666 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mapping.api; + +import org.opendaylight.controller.netconf.api.NetconfSession; + +public interface SessionAwareNetconfOperation extends NetconfOperation { + + void setSession(NetconfSession session); +} diff --git a/opendaylight/netconf/netconf-notifications-api/pom.xml b/opendaylight/netconf/netconf-notifications-api/pom.xml new file mode 100644 index 0000000000..a1fbe150ae --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/pom.xml @@ -0,0 +1,54 @@ + + + + + + netconf-subsystem + org.opendaylight.controller + 0.3.0-SNAPSHOT + + 4.0.0 + bundle + netconf-notifications-api + + + + org.opendaylight.controller + netconf-api + + + org.opendaylight.controller + ietf-netconf-notifications + + + org.slf4j + slf4j-api + + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.notifications.* + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNetconfNotificationListener.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNetconfNotificationListener.java new file mode 100644 index 0000000000..899ab85e92 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNetconfNotificationListener.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; + + +/** + * Listener for base netconf notifications defined in https://tools.ietf.org/html/rfc6470. + * This listener uses generated classes from yang model defined in RFC6470. + * It alleviates the provisioning of base netconf notifications from the code. + */ +public interface BaseNetconfNotificationListener { + + /** + * Callback used to notify about a change in used capabilities + */ + void onCapabilityChanged(NetconfCapabilityChange capabilityChange); + + // TODO add other base notifications + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNotificationPublisherRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNotificationPublisherRegistration.java new file mode 100644 index 0000000000..7755fc5b2c --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNotificationPublisherRegistration.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Registration for base notification publisher. This registration allows for publishing of base netconf notifications using generated classes + */ +public interface BaseNotificationPublisherRegistration extends NotificationRegistration, BaseNetconfNotificationListener { + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotification.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotification.java new file mode 100644 index 0000000000..efa42c03e9 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotification.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import com.google.common.base.Preconditions; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Special kind of netconf message that contains a timestamp. + */ +public final class NetconfNotification extends NetconfMessage { + + public static final String NOTIFICATION = "notification"; + public static final String NOTIFICATION_NAMESPACE = "urn:ietf:params:netconf:capability:notification:1.0"; + public static final String RFC3339_DATE_FORMAT_BLUEPRINT = "yyyy-MM-dd'T'HH:mm:ssXXX"; + public static final String EVENT_TIME = "eventTime"; + + /** + * Create new notification and capture the timestamp in the constructor + */ + public NetconfNotification(final Document notificationContent) { + this(notificationContent, new Date()); + } + + /** + * Create new notification with provided timestamp + */ + public NetconfNotification(final Document notificationContent, final Date eventTime) { + super(wrapNotification(notificationContent, eventTime)); + } + + private static Document wrapNotification(final Document notificationContent, final Date eventTime) { + Preconditions.checkNotNull(notificationContent); + Preconditions.checkNotNull(eventTime); + + final Element baseNotification = notificationContent.getDocumentElement(); + final Element entireNotification = notificationContent.createElementNS(NOTIFICATION_NAMESPACE, NOTIFICATION); + entireNotification.appendChild(baseNotification); + + final Element eventTimeElement = notificationContent.createElementNS(NOTIFICATION_NAMESPACE, EVENT_TIME); + eventTimeElement.setTextContent(getSerializedEventTime(eventTime)); + entireNotification.appendChild(eventTimeElement); + + notificationContent.appendChild(entireNotification); + return notificationContent; + } + + private static String getSerializedEventTime(final Date eventTime) { + // SimpleDateFormat is not threadsafe, cannot be in a constant + return new SimpleDateFormat(RFC3339_DATE_FORMAT_BLUEPRINT).format(eventTime); + } +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationCollector.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationCollector.java new file mode 100644 index 0000000000..2663a5db5f --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationCollector.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.Stream; + +/** + * Collector of all notifications. Base or generic + */ +public interface NetconfNotificationCollector { + + /** + * Add notification publisher for a particular stream + * + * Implementations should allow for multiple publishers of a single stream + * and its up to implementations to decide how to merge metadata (e.g. description) + * for the same stream when providing information about available stream + * + */ + NotificationPublisherRegistration registerNotificationPublisher(Stream stream); + + /** + * Register base notification publisher + */ + BaseNotificationPublisherRegistration registerBaseNotificationPublisher(); + + /** + * Users of the registry have an option to get notification each time new notification stream gets registered + * This allows for a push model in addition to pull model for retrieving information about available streams. + * + * The listener should receive callbacks for each stream available prior to the registration when its registered + */ + NotificationRegistration registerStreamListener(NetconfNotificationStreamListener listener); + + /** + * Simple listener that receives notifications about changes in stream availability + */ + public interface NetconfNotificationStreamListener { + + /** + * Stream becomes available in the collector (first publisher is registered) + */ + void onStreamRegistered(Stream stream); + + /** + * Stream is not available anymore in the collector (last publisher is unregistered) + */ + void onStreamUnregistered(StreamNameType stream); + } + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationListener.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationListener.java new file mode 100644 index 0000000000..e1da05cd2b --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationListener.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; + +/** + * Generic listener for netconf notifications + */ +public interface NetconfNotificationListener { + + /** + * Callback used to notify the listener about any new notification + */ + void onNotification(StreamNameType stream, NetconfNotification notification); + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationRegistry.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationRegistry.java new file mode 100644 index 0000000000..db2443ec79 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationRegistry.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; + +/** + * + */ +public interface NetconfNotificationRegistry { + + /** + * Add listener for a certain notification type + */ + NotificationListenerRegistration registerNotificationListener(StreamNameType stream, NetconfNotificationListener listener); + + /** + * Check stream availability + */ + boolean isStreamAvailable(StreamNameType streamNameType); + + /** + * Get all the streams available + */ + Streams getNotificationPublishers(); + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationListenerRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationListenerRegistration.java new file mode 100644 index 0000000000..aa8161277c --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationListenerRegistration.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Manages the registration of a single listener + */ +public interface NotificationListenerRegistration extends NotificationRegistration { + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationPublisherRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationPublisherRegistration.java new file mode 100644 index 0000000000..de105fcc0a --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationPublisherRegistration.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Registration for notification publisher. This registration allows for publishing any netconf notifications + */ +public interface NotificationPublisherRegistration extends NetconfNotificationListener, NotificationRegistration { + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationRegistration.java new file mode 100644 index 0000000000..a7a86a4f7e --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationRegistration.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Generic registration, used as a base for other registration types + */ +public interface NotificationRegistration extends AutoCloseable { + + // Overriden close does not throw any kind of checked exception + + /** + * Close the registration. + */ + @Override + void close(); +} diff --git a/opendaylight/netconf/netconf-notifications-impl/pom.xml b/opendaylight/netconf/netconf-notifications-impl/pom.xml new file mode 100644 index 0000000000..510d9f07e3 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/pom.xml @@ -0,0 +1,70 @@ + + + + + + netconf-subsystem + org.opendaylight.controller + 0.3.0-SNAPSHOT + + 4.0.0 + bundle + netconf-notifications-impl + + + + org.opendaylight.controller + netconf-notifications-api + + + ${project.groupId} + netconf-util + + + org.opendaylight.yangtools + binding-generator-impl + + + org.opendaylight.yangtools + binding-data-codec + + + org.slf4j + slf4j-api + + + xmlunit + xmlunit + + + org.opendaylight.yangtools + mockito-configuration + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.notifications.impl.osgi.Activator + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManager.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManager.java new file mode 100644 index 0000000000..d2dbcaf416 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManager.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; +import java.util.Map; +import java.util.Set; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; +import org.opendaylight.controller.netconf.notifications.BaseNotificationPublisherRegistration; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationCollector; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.controller.netconf.notifications.NotificationPublisherRegistration; +import org.opendaylight.controller.netconf.notifications.NotificationRegistration; +import org.opendaylight.controller.netconf.notifications.impl.ops.NotificationsTransformUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.StreamsBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.Stream; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamKey; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ThreadSafe +public class NetconfNotificationManager implements NetconfNotificationCollector, NetconfNotificationRegistry, NetconfNotificationListener, AutoCloseable { + + public static final StreamNameType BASE_STREAM_NAME = new StreamNameType("NETCONF"); + public static final Stream BASE_NETCONF_STREAM; + + static { + BASE_NETCONF_STREAM = new StreamBuilder() + .setName(BASE_STREAM_NAME) + .setKey(new StreamKey(BASE_STREAM_NAME)) + .setReplaySupport(false) + .setDescription("Default Event Stream") + .build(); + } + + private static final Logger LOG = LoggerFactory.getLogger(NetconfNotificationManager.class); + + // TODO excessive synchronization provides thread safety but is most likely not optimal (combination of concurrent collections might improve performance) + // And also calling callbacks from a synchronized block is dangerous since the listeners/publishers can block the whole notification processing + + @GuardedBy("this") + private final Multimap notificationListeners = HashMultimap.create(); + + @GuardedBy("this") + private final Set streamListeners = Sets.newHashSet(); + + @GuardedBy("this") + private final Map streamMetadata = Maps.newHashMap(); + + @GuardedBy("this") + private final Multiset availableStreams = HashMultiset.create(); + + @GuardedBy("this") + private final Set notificationPublishers = Sets.newHashSet(); + + @Override + public synchronized void onNotification(final StreamNameType stream, final NetconfNotification notification) { + LOG.debug("Notification of type {} detected", stream); + if(LOG.isTraceEnabled()) { + LOG.debug("Notification of type {} detected: {}", stream, notification); + } + + for (final GenericNotificationListenerReg listenerReg : notificationListeners.get(BASE_STREAM_NAME)) { + listenerReg.getListener().onNotification(BASE_STREAM_NAME, notification); + } + } + + @Override + public synchronized NotificationListenerRegistration registerNotificationListener(final StreamNameType stream, final NetconfNotificationListener listener) { + Preconditions.checkNotNull(stream); + Preconditions.checkNotNull(listener); + + LOG.trace("Notification listener registered for stream: {}", stream); + + final GenericNotificationListenerReg genericNotificationListenerReg = new GenericNotificationListenerReg(listener) { + @Override + public void close() { + synchronized (NetconfNotificationManager.this) { + LOG.trace("Notification listener unregistered for stream: {}", stream); + super.close(); + } + } + }; + + notificationListeners.put(BASE_STREAM_NAME, genericNotificationListenerReg); + return genericNotificationListenerReg; + } + + @Override + public synchronized Streams getNotificationPublishers() { + return new StreamsBuilder().setStream(Lists.newArrayList(streamMetadata.values())).build(); + } + + @Override + public synchronized boolean isStreamAvailable(final StreamNameType streamNameType) { + return availableStreams.contains(streamNameType); + } + + @Override + public synchronized NotificationRegistration registerStreamListener(final NetconfNotificationStreamListener listener) { + streamListeners.add(listener); + + // Notify about all already available + for (final Stream availableStream : streamMetadata.values()) { + listener.onStreamRegistered(availableStream); + } + + return new NotificationRegistration() { + @Override + public void close() { + synchronized(NetconfNotificationManager.this) { + streamListeners.remove(listener); + } + } + }; + } + + @Override + public synchronized void close() { + // Unregister all listeners + for (final GenericNotificationListenerReg genericNotificationListenerReg : notificationListeners.values()) { + genericNotificationListenerReg.close(); + } + notificationListeners.clear(); + + // Unregister all publishers + for (final GenericNotificationPublisherReg notificationPublisher : notificationPublishers) { + notificationPublisher.close(); + } + notificationPublishers.clear(); + + // Clear stream Listeners + streamListeners.clear(); + } + + @Override + public synchronized NotificationPublisherRegistration registerNotificationPublisher(final Stream stream) { + Preconditions.checkNotNull(stream); + final StreamNameType streamName = stream.getName(); + + LOG.debug("Notification publisher registered for stream: {}", streamName); + if(LOG.isTraceEnabled()) { + LOG.trace("Notification publisher registered for stream: {}", stream); + } + + if(streamMetadata.containsKey(streamName)) { + LOG.warn("Notification stream {} already registered as: {}. Will be reused", streamName, streamMetadata.get(streamName)); + } else { + streamMetadata.put(streamName, stream); + } + + availableStreams.add(streamName); + + final GenericNotificationPublisherReg genericNotificationPublisherReg = new GenericNotificationPublisherReg(this, streamName) { + @Override + public void close() { + synchronized (NetconfNotificationManager.this) { + super.close(); + } + } + }; + + notificationPublishers.add(genericNotificationPublisherReg); + + notifyStreamAdded(stream); + return genericNotificationPublisherReg; + } + + private void unregisterNotificationPublisher(final StreamNameType streamName, final GenericNotificationPublisherReg genericNotificationPublisherReg) { + availableStreams.remove(streamName); + notificationPublishers.remove(genericNotificationPublisherReg); + + LOG.debug("Notification publisher unregistered for stream: {}", streamName); + + // Notify stream listeners if all publishers are gone and also clear metadata for stream + if (!isStreamAvailable(streamName)) { + LOG.debug("Notification stream: {} became unavailable", streamName); + streamMetadata.remove(streamName); + notifyStreamRemoved(streamName); + } + } + + private synchronized void notifyStreamAdded(final Stream stream) { + for (final NetconfNotificationStreamListener streamListener : streamListeners) { + streamListener.onStreamRegistered(stream); + } + } + private synchronized void notifyStreamRemoved(final StreamNameType stream) { + for (final NetconfNotificationStreamListener streamListener : streamListeners) { + streamListener.onStreamUnregistered(stream); + } + } + + @Override + public BaseNotificationPublisherRegistration registerBaseNotificationPublisher() { + final NotificationPublisherRegistration notificationPublisherRegistration = registerNotificationPublisher(BASE_NETCONF_STREAM); + return new BaseNotificationPublisherReg(notificationPublisherRegistration); + } + + private static class GenericNotificationPublisherReg implements NotificationPublisherRegistration { + private NetconfNotificationManager baseListener; + private final StreamNameType registeredStream; + + public GenericNotificationPublisherReg(final NetconfNotificationManager baseListener, final StreamNameType registeredStream) { + this.baseListener = baseListener; + this.registeredStream = registeredStream; + } + + @Override + public void close() { + baseListener.unregisterNotificationPublisher(registeredStream, this); + baseListener = null; + } + + @Override + public void onNotification(final StreamNameType stream, final NetconfNotification notification) { + Preconditions.checkState(baseListener != null, "Already closed"); + Preconditions.checkArgument(stream.equals(registeredStream)); + baseListener.onNotification(stream, notification); + } + } + + private static class BaseNotificationPublisherReg implements BaseNotificationPublisherRegistration { + + private final NotificationPublisherRegistration baseRegistration; + + public BaseNotificationPublisherReg(final NotificationPublisherRegistration baseRegistration) { + this.baseRegistration = baseRegistration; + } + + @Override + public void close() { + baseRegistration.close(); + } + + @Override + public void onCapabilityChanged(final NetconfCapabilityChange capabilityChange) { + baseRegistration.onNotification(BASE_STREAM_NAME, serializeNotification(capabilityChange)); + } + + private static NetconfNotification serializeNotification(final NetconfCapabilityChange capabilityChange) { + return NotificationsTransformUtil.transform(capabilityChange); + } + } + + private class GenericNotificationListenerReg implements NotificationListenerRegistration { + private final NetconfNotificationListener listener; + + public GenericNotificationListenerReg(final NetconfNotificationListener listener) { + this.listener = listener; + } + + public NetconfNotificationListener getListener() { + return listener; + } + + @Override + public void close() { + notificationListeners.remove(BASE_STREAM_NAME, this); + } + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscription.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscription.java new file mode 100644 index 0000000000..e8b7413069 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscription.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.util.List; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mapping.api.SessionAwareNetconfOperation; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.controller.netconf.notifications.impl.NetconfNotificationManager; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Create subscription listens for create subscription requests and registers notification listeners into notification registry. + * Received notifications are sent to the client right away + */ +public class CreateSubscription extends AbstractLastNetconfOperation implements SessionAwareNetconfOperation, AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(CreateSubscription.class); + + static final String CREATE_SUBSCRIPTION = "create-subscription"; + + private final NetconfNotificationRegistry notifications; + private final List subscriptions = Lists.newArrayList(); + private NetconfSession netconfSession; + + public CreateSubscription(final String netconfSessionIdForReporting, final NetconfNotificationRegistry notifications) { + super(netconfSessionIdForReporting); + this.notifications = notifications; + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + operationElement.checkName(CREATE_SUBSCRIPTION); + operationElement.checkNamespace(CreateSubscriptionInput.QNAME.getNamespace().toString()); + // FIXME reimplement using CODEC_REGISTRY and parse everything into generated class instance + // Waiting ofr https://git.opendaylight.org/gerrit/#/c/13763/ + + // FIXME filter could be supported same way as netconf server filters get and get-config results + final Optional filter = operationElement.getOnlyChildElementWithSameNamespaceOptionally("filter"); + Preconditions.checkArgument(filter.isPresent() == false, "Filter element not yet supported"); + + // Replay not supported + final Optional startTime = operationElement.getOnlyChildElementWithSameNamespaceOptionally("startTime"); + Preconditions.checkArgument(startTime.isPresent() == false, "StartTime element not yet supported"); + + // Stop time not supported + final Optional stopTime = operationElement.getOnlyChildElementWithSameNamespaceOptionally("stopTime"); + Preconditions.checkArgument(stopTime.isPresent() == false, "StopTime element not yet supported"); + + final StreamNameType streamNameType = parseStreamIfPresent(operationElement); + + Preconditions.checkNotNull(netconfSession); + // Premature streams are allowed (meaning listener can register even if no provider is available yet) + if(notifications.isStreamAvailable(streamNameType) == false) { + LOG.warn("Registering premature stream {}. No publisher available yet for session {}", streamNameType, getNetconfSessionIdForReporting()); + } + + final NotificationListenerRegistration notificationListenerRegistration = + notifications.registerNotificationListener(streamNameType, new NotificationSubscription(netconfSession)); + subscriptions.add(notificationListenerRegistration); + + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + private StreamNameType parseStreamIfPresent(final XmlElement operationElement) throws NetconfDocumentedException { + final Optional stream = operationElement.getOnlyChildElementWithSameNamespaceOptionally("stream"); + return stream.isPresent() ? new StreamNameType(stream.get().getTextContent()) : NetconfNotificationManager.BASE_STREAM_NAME; + } + + @Override + protected String getOperationName() { + return CREATE_SUBSCRIPTION; + } + + @Override + protected String getOperationNamespace() { + return CreateSubscriptionInput.QNAME.getNamespace().toString(); + } + + @Override + public void setSession(final NetconfSession session) { + this.netconfSession = session; + } + + @Override + public void close() { + netconfSession = null; + // Unregister from notification streams + for (final NotificationListenerRegistration subscription : subscriptions) { + subscription.close(); + } + } + + private static class NotificationSubscription implements NetconfNotificationListener { + private final NetconfSession currentSession; + + public NotificationSubscription(final NetconfSession currentSession) { + this.currentSession = currentSession; + } + + @Override + public void onNotification(final StreamNameType stream, final NetconfNotification notification) { + currentSession.sendMessage(notification); + } + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/Get.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/Get.java new file mode 100644 index 0000000000..85f29360c5 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/Get.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.dom.DOMResult; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.Netconf; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.NetconfBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Serialize the subtree for netconf notifications into the response of get rpc. + * This operation just adds its subtree into the common response of get rpc. + */ +public class Get extends AbstractNetconfOperation implements AutoCloseable { + + private static final String GET = "get"; + private static final InstanceIdentifier NETCONF_SUBTREE_INSTANCE_IDENTIFIER = InstanceIdentifier.builder(Netconf.class).build(); + + private final NetconfNotificationRegistry notificationRegistry; + + public Get(final String netconfSessionIdForReporting, final NetconfNotificationRegistry notificationRegistry) { + super(netconfSessionIdForReporting); + Preconditions.checkNotNull(notificationRegistry); + this.notificationRegistry = notificationRegistry; + } + + @Override + protected String getOperationName() { + return GET; + } + + @Override + public Document handle(final Document requestMessage, final NetconfOperationChainedExecution subsequentOperation) throws NetconfDocumentedException { + final Document partialResponse = subsequentOperation.execute(requestMessage); + final Streams availableStreams = notificationRegistry.getNotificationPublishers(); + if(availableStreams.getStream().isEmpty() == false) { + serializeStreamsSubtree(partialResponse, availableStreams); + } + return partialResponse; + } + + static void serializeStreamsSubtree(final Document partialResponse, final Streams availableStreams) throws NetconfDocumentedException { + final Netconf netconfSubtree = new NetconfBuilder().setStreams(availableStreams).build(); + final NormalizedNode normalized = toNormalized(netconfSubtree); + + final DOMResult result = new DOMResult(getPlaceholder(partialResponse)); + + try { + NotificationsTransformUtil.writeNormalizedNode(normalized, result, SchemaPath.ROOT); + } catch (final XMLStreamException | IOException e) { + throw new IllegalStateException("Unable to serialize " + netconfSubtree, e); + } + } + + private static Element getPlaceholder(final Document innerResult) + throws NetconfDocumentedException { + final XmlElement rootElement = XmlElement.fromDomElementWithExpected( + innerResult.getDocumentElement(), XmlNetconfConstants.RPC_REPLY_KEY, XmlNetconfConstants.RFC4741_TARGET_NAMESPACE); + return rootElement.getOnlyChildElement(XmlNetconfConstants.DATA_KEY).getDomElement(); + } + + private static NormalizedNode toNormalized(final Netconf netconfSubtree) { + return NotificationsTransformUtil.CODEC_REGISTRY.toNormalizedNode(NETCONF_SUBTREE_INSTANCE_IDENTIFIER, netconfSubtree).getValue(); + } + + @Override + protected Element handle(final Document document, final XmlElement message, final NetconfOperationChainedExecution subsequentOperation) + throws NetconfDocumentedException { + throw new UnsupportedOperationException("Never gets called"); + } + + @Override + protected HandlingPriority getHandlingPriority() { + return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY.increasePriority(2); + } + + @Override + public void close() throws Exception { + + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtil.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtil.java new file mode 100644 index 0000000000..080176dcd4 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtil.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import javassist.ClassPool; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.dom.DOMResult; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.$YangModuleInfoImpl; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator; +import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext; +import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; + +public final class NotificationsTransformUtil { + + private static final Logger LOG = LoggerFactory.getLogger(NotificationsTransformUtil.class); + + private NotificationsTransformUtil() {} + + static final SchemaContext NOTIFICATIONS_SCHEMA_CTX; + static final BindingNormalizedNodeCodecRegistry CODEC_REGISTRY; + static final XMLOutputFactory XML_FACTORY; + static final RpcDefinition CREATE_SUBSCRIPTION_RPC; + + static final SchemaPath CAPABILITY_CHANGE_SCHEMA_PATH = SchemaPath.create(true, NetconfCapabilityChange.QNAME); + + static { + XML_FACTORY = XMLOutputFactory.newFactory(); + XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + + final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create(); + moduleInfoBackedContext.addModuleInfos(Collections.singletonList($YangModuleInfoImpl.getInstance())); + moduleInfoBackedContext.addModuleInfos(Collections.singletonList(org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.$YangModuleInfoImpl.getInstance())); + final Optional schemaContextOptional = moduleInfoBackedContext.tryToCreateSchemaContext(); + Preconditions.checkState(schemaContextOptional.isPresent()); + NOTIFICATIONS_SCHEMA_CTX = schemaContextOptional.get(); + + CREATE_SUBSCRIPTION_RPC = Preconditions.checkNotNull(findCreateSubscriptionRpc()); + + Preconditions.checkNotNull(CREATE_SUBSCRIPTION_RPC); + + final JavassistUtils javassist = JavassistUtils.forClassPool(ClassPool.getDefault()); + CODEC_REGISTRY = new BindingNormalizedNodeCodecRegistry(StreamWriterGenerator.create(javassist)); + CODEC_REGISTRY.onBindingRuntimeContextUpdated(BindingRuntimeContext.create(moduleInfoBackedContext, NOTIFICATIONS_SCHEMA_CTX)); + } + + private static RpcDefinition findCreateSubscriptionRpc() { + return Iterables.getFirst(Collections2.filter(NOTIFICATIONS_SCHEMA_CTX.getOperations(), new Predicate() { + @Override + public boolean apply(final RpcDefinition input) { + return input.getQName().getLocalName().equals(CreateSubscription.CREATE_SUBSCRIPTION); + } + }), null); + } + + /** + * Transform base notification for capabilities into NetconfNotification + */ + public static NetconfNotification transform(final NetconfCapabilityChange capabilityChange) { + return transform(capabilityChange, Optional.absent()); + } + + public static NetconfNotification transform(final NetconfCapabilityChange capabilityChange, final Date eventTime) { + return transform(capabilityChange, Optional.fromNullable(eventTime)); + } + + private static NetconfNotification transform(final NetconfCapabilityChange capabilityChange, final Optional eventTime) { + final ContainerNode containerNode = CODEC_REGISTRY.toNormalizedNodeNotification(capabilityChange); + final DOMResult result = new DOMResult(XmlUtil.newDocument()); + try { + writeNormalizedNode(containerNode, result, CAPABILITY_CHANGE_SCHEMA_PATH); + } catch (final XMLStreamException| IOException e) { + throw new IllegalStateException("Unable to serialize " + capabilityChange, e); + } + final Document node = (Document) result.getNode(); + return eventTime.isPresent() ? + new NetconfNotification(node, eventTime.get()): + new NetconfNotification(node); + } + + static void writeNormalizedNode(final NormalizedNode normalized, final DOMResult result, final SchemaPath schemaPath) throws IOException, XMLStreamException { + NormalizedNodeWriter normalizedNodeWriter = null; + NormalizedNodeStreamWriter normalizedNodeStreamWriter = null; + XMLStreamWriter writer = null; + try { + writer = XML_FACTORY.createXMLStreamWriter(result); + normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, NOTIFICATIONS_SCHEMA_CTX, schemaPath); + normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(normalizedNodeStreamWriter); + + normalizedNodeWriter.write(normalized); + + normalizedNodeWriter.flush(); + } finally { + try { + if(normalizedNodeWriter != null) { + normalizedNodeWriter.close(); + } + if(normalizedNodeStreamWriter != null) { + normalizedNodeStreamWriter.close(); + } + if(writer != null) { + writer.close(); + } + } catch (final Exception e) { + LOG.warn("Unable to close resource properly", e); + } + } + } + +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/osgi/Activator.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/osgi/Activator.java new file mode 100644 index 0000000000..ef950f8a78 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/osgi/Activator.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.osgi; + +import com.google.common.base.Optional; +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Set; +import org.opendaylight.controller.netconf.mapping.api.Capability; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationCollector; +import org.opendaylight.controller.netconf.notifications.impl.NetconfNotificationManager; +import org.opendaylight.controller.netconf.notifications.impl.ops.CreateSubscription; +import org.opendaylight.controller.netconf.notifications.impl.ops.Get; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +public class Activator implements BundleActivator { + + private ServiceRegistration netconfNotificationCollectorServiceRegistration; + private ServiceRegistration operationaServiceRegistration; + private NetconfNotificationManager netconfNotificationManager; + + @Override + public void start(final BundleContext context) throws Exception { + netconfNotificationManager = new NetconfNotificationManager(); + netconfNotificationCollectorServiceRegistration = context.registerService(NetconfNotificationCollector.class, netconfNotificationManager, new Hashtable()); + + final NetconfOperationServiceFactory netconfOperationServiceFactory = new NetconfOperationServiceFactory() { + + @Override + public NetconfOperationService createService(final String netconfSessionIdForReporting) { + return new NetconfOperationService() { + + private final CreateSubscription createSubscription = new CreateSubscription(netconfSessionIdForReporting, netconfNotificationManager); + + @Override + public Set getCapabilities() { + return Collections.singleton(new NotificationsCapability()); + } + + @Override + public Set getNetconfOperations() { + return Sets.newHashSet( + new Get(netconfSessionIdForReporting, netconfNotificationManager), + createSubscription); + } + + @Override + public void close() { + createSubscription.close(); + } + }; + } + }; + + operationaServiceRegistration = context.registerService(NetconfOperationServiceFactory.class, netconfOperationServiceFactory, new Hashtable()); + + } + + @Override + public void stop(final BundleContext context) throws Exception { + if(netconfNotificationCollectorServiceRegistration != null) { + netconfNotificationCollectorServiceRegistration.unregister(); + netconfNotificationCollectorServiceRegistration = null; + } + if (netconfNotificationManager != null) { + netconfNotificationManager.close(); + } + if (operationaServiceRegistration != null) { + operationaServiceRegistration.unregister(); + operationaServiceRegistration = null; + } + } + + private class NotificationsCapability implements Capability { + @Override + public String getCapabilityUri() { + return NetconfNotification.NOTIFICATION_NAMESPACE; + } + + @Override + public Optional getModuleNamespace() { + return Optional.absent(); + } + + @Override + public Optional getModuleName() { + return Optional.absent(); + } + + @Override + public Optional getRevision() { + return Optional.absent(); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + + @Override + public Collection getLocation() { + return Collections.emptyList(); + } + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManagerTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManagerTest.java new file mode 100644 index 0000000000..36d2015ab7 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManagerTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl; + +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.netconf.notifications.BaseNotificationPublisherRegistration; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationCollector; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.Stream; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChangeBuilder; + +public class NetconfNotificationManagerTest { + + @Mock + private NetconfNotificationRegistry notificationRegistry; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNotificationListeners() throws Exception { + final NetconfNotificationManager netconfNotificationManager = new NetconfNotificationManager(); + final BaseNotificationPublisherRegistration baseNotificationPublisherRegistration = + netconfNotificationManager.registerBaseNotificationPublisher(); + + final NetconfCapabilityChangeBuilder capabilityChangedBuilder = new NetconfCapabilityChangeBuilder(); + + final NetconfNotificationListener listener = mock(NetconfNotificationListener.class); + doNothing().when(listener).onNotification(any(StreamNameType.class), any(NetconfNotification.class)); + final NotificationListenerRegistration notificationListenerRegistration = netconfNotificationManager.registerNotificationListener(NetconfNotificationManager.BASE_NETCONF_STREAM.getName(), listener); + final NetconfCapabilityChange notification = capabilityChangedBuilder.build(); + baseNotificationPublisherRegistration.onCapabilityChanged(notification); + + verify(listener).onNotification(any(StreamNameType.class), any(NetconfNotification.class)); + + notificationListenerRegistration.close(); + + baseNotificationPublisherRegistration.onCapabilityChanged(notification); + verifyNoMoreInteractions(listener); + } + + @Test + public void testClose() throws Exception { + final NetconfNotificationManager netconfNotificationManager = new NetconfNotificationManager(); + + final BaseNotificationPublisherRegistration baseNotificationPublisherRegistration = netconfNotificationManager.registerBaseNotificationPublisher(); + + final NetconfNotificationListener listener = mock(NetconfNotificationListener.class); + doNothing().when(listener).onNotification(any(StreamNameType.class), any(NetconfNotification.class)); + + netconfNotificationManager.registerNotificationListener(NetconfNotificationManager.BASE_NETCONF_STREAM.getName(), listener); + + final NetconfNotificationCollector.NetconfNotificationStreamListener streamListener = + mock(NetconfNotificationCollector.NetconfNotificationStreamListener.class); + doNothing().when(streamListener).onStreamUnregistered(any(StreamNameType.class)); + doNothing().when(streamListener).onStreamRegistered(any(Stream.class)); + netconfNotificationManager.registerStreamListener(streamListener); + + verify(streamListener).onStreamRegistered(NetconfNotificationManager.BASE_NETCONF_STREAM); + + netconfNotificationManager.close(); + + verify(streamListener).onStreamUnregistered(NetconfNotificationManager.BASE_NETCONF_STREAM.getName()); + + try { + baseNotificationPublisherRegistration.onCapabilityChanged(new NetconfCapabilityChangeBuilder().build()); + } catch (final IllegalStateException e) { + // Exception should be thrown after manager is closed + return; + } + + fail("Publishing into a closed manager should fail"); + } + + @Test + public void testStreamListeners() throws Exception { + final NetconfNotificationManager netconfNotificationManager = new NetconfNotificationManager(); + + final NetconfNotificationCollector.NetconfNotificationStreamListener streamListener = mock(NetconfNotificationCollector.NetconfNotificationStreamListener.class); + doNothing().when(streamListener).onStreamRegistered(any(Stream.class)); + doNothing().when(streamListener).onStreamUnregistered(any(StreamNameType.class)); + + netconfNotificationManager.registerStreamListener(streamListener); + + final BaseNotificationPublisherRegistration baseNotificationPublisherRegistration = + netconfNotificationManager.registerBaseNotificationPublisher(); + + verify(streamListener).onStreamRegistered(NetconfNotificationManager.BASE_NETCONF_STREAM); + + + baseNotificationPublisherRegistration.close(); + + verify(streamListener).onStreamUnregistered(NetconfNotificationManager.BASE_STREAM_NAME); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscriptionTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscriptionTest.java new file mode 100644 index 0000000000..aca8f2de91 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscriptionTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.w3c.dom.Element; + +public class CreateSubscriptionTest { + + private static final String CREATE_SUBSCRIPTION_XML = "\n" + + "TESTSTREAM" + + ""; + + @Mock + private NetconfNotificationRegistry notificationRegistry; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(true).when(notificationRegistry).isStreamAvailable(any(StreamNameType.class)); + doReturn(mock(NotificationListenerRegistration.class)).when(notificationRegistry).registerNotificationListener(any(StreamNameType.class), any(NetconfNotificationListener.class)); + } + + @Test + public void testHandleWithNoSubsequentOperations() throws Exception { + final CreateSubscription createSubscription = new CreateSubscription("id", notificationRegistry); + createSubscription.setSession(mock(NetconfSession.class)); + + final Element e = XmlUtil.readXmlToElement(CREATE_SUBSCRIPTION_XML); + + final XmlElement operationElement = XmlElement.fromDomElement(e); + final Element element = createSubscription.handleWithNoSubsequentOperations(XmlUtil.newDocument(), operationElement); + + Assert.assertThat(XmlUtil.toString(element), CoreMatchers.containsString("ok")); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/GetTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/GetTest.java new file mode 100644 index 0000000000..6f38f24f10 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/GetTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.Lists; +import java.io.IOException; +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.Test; +import org.opendaylight.controller.netconf.notifications.impl.ops.Get; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.StreamsBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamKey; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +public class GetTest { + + @Test + public void testSerializeStreamsSubtree() throws Exception { + final StreamsBuilder streamsBuilder = new StreamsBuilder(); + final StreamBuilder streamBuilder = new StreamBuilder(); + final StreamNameType base = new StreamNameType("base"); + streamBuilder.setName(base); + streamBuilder.setKey(new StreamKey(base)); + streamBuilder.setDescription("description"); + streamBuilder.setReplaySupport(false); + streamsBuilder.setStream(Lists.newArrayList(streamBuilder.build())); + final Streams streams = streamsBuilder.build(); + + final Document response = getBlankResponse(); + Get.serializeStreamsSubtree(response, streams); + final Diff diff = XMLUnit.compareXML(XmlUtil.toString(response), + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "base\n" + + "description\n" + + "false\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"); + + assertTrue(diff.toString(), diff.identical()); + } + + private Document getBlankResponse() throws IOException, SAXException { + + return XmlUtil.readXmlToDocument("\n" + + "\n" + + "\n" + + ""); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtilTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtilTest.java new file mode 100644 index 0000000000..c4bc41cf0f --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtilTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.Lists; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.Test; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChangeBuilder; + +public class NotificationsTransformUtilTest { + + private static final Date DATE = new Date(); + private static final String innerNotification = "" + + "uri3" + + "uri4" + + "uri1" + + ""; + + private static final String expectedNotification = "" + + innerNotification + + "" + new SimpleDateFormat(NetconfNotification.RFC3339_DATE_FORMAT_BLUEPRINT).format(DATE) + "" + + ""; + + @Test + public void testTransform() throws Exception { + final NetconfCapabilityChangeBuilder netconfCapabilityChangeBuilder = new NetconfCapabilityChangeBuilder(); + + netconfCapabilityChangeBuilder.setAddedCapability(Lists.newArrayList(new Uri("uri1"), new Uri("uri1"))); + netconfCapabilityChangeBuilder.setDeletedCapability(Lists.newArrayList(new Uri("uri3"), new Uri("uri4"))); + + final NetconfCapabilityChange capabilityChange = netconfCapabilityChangeBuilder.build(); + final NetconfNotification transform = NotificationsTransformUtil.transform(capabilityChange, DATE); + + final String serialized = XmlUtil.toString(transform.getDocument()); + + XMLUnit.setIgnoreWhitespace(true); + final Diff diff = XMLUnit.compareXML(expectedNotification, serialized); + assertTrue(diff.toString(), diff.similar()); + } + + @Test + public void testTransformFromDOM() throws Exception { + final NetconfNotification netconfNotification = new NetconfNotification(XmlUtil.readXmlToDocument(innerNotification), DATE); + + XMLUnit.setIgnoreWhitespace(true); + final Diff diff = XMLUnit.compareXML(expectedNotification, netconfNotification.toString()); + assertTrue(diff.toString(), diff.similar()); + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index 2a5ba09673..653dd70b29 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -28,12 +28,16 @@ netconf-ssh netconf-tcp netconf-monitoring + ietf-netconf ietf-netconf-monitoring + ietf-netconf-notifications ietf-netconf-monitoring-extension netconf-connector-config netconf-auth netconf-usermanager netconf-testtool + netconf-notifications-impl + netconf-notifications-api netconf-artifacts