--- /dev/null
+/*
+ * 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.md.sal.dom.api;
+
+import java.util.Date;
+
+/**
+ * Generic event interface
+ */
+public interface DOMEvent {
+
+ /**
+ * Get the time of the event occurrence
+ *
+ * @return the event time
+ */
+ Date getEventTime();
+}
<groupId>org.opendaylight.controller</groupId>
<artifactId>netconf-client</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-notifications-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>netty-config-api</artifactId>
*/
package org.opendaylight.controller.sal.connect.api;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
public interface MessageTransformer<M> {
- ContainerNode toNotification(M message);
+ DOMNotification toNotification(M message);
M toRpcRequest(SchemaPath rpc, NormalizedNode<?, ?> node);
*/
package org.opendaylight.controller.sal.connect.api;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public interface RemoteDeviceHandler<PREF> extends AutoCloseable {
void onDeviceFailed(Throwable throwable);
- void onNotification(ContainerNode domNotification);
+ void onNotification(DOMNotification domNotification);
void close();
}
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability;
import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
final NotificationHandler.NotificationFilter filter = new NotificationHandler.NotificationFilter() {
@Override
- public Optional<NormalizedNode<?, ?>> filterNotification(final NormalizedNode<?, ?> notification) {
+ public Optional<DOMNotification> filterNotification(final DOMNotification notification) {
if (isCapabilityChanged(notification)) {
logger.info("{}: Schemas change detected, reconnecting", id);
// Only disconnect is enough, the reconnecting nature of the connector will take care of reconnecting
listener.disconnect();
return Optional.absent();
}
- return Optional.<NormalizedNode<?, ?>>of(notification);
+ return Optional.of(notification);
}
- private boolean isCapabilityChanged(final NormalizedNode<?, ?> notification) {
- return notification.getNodeType().equals(NetconfCapabilityChange.QNAME);
+ private boolean isCapabilityChanged(final DOMNotification notification) {
+ return notification.getBody().getNodeType().equals(NetconfCapabilityChange.QNAME);
}
};
import com.google.common.base.Preconditions;
import java.util.LinkedList;
import java.util.List;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.netconf.api.NetconfMessage;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.opendaylight.controller.sal.connect.api.MessageTransformer;
import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler;
import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
queue.clear();
}
- private ContainerNode transformNotification(final NetconfMessage cachedNotification) {
- final ContainerNode parsedNotification = messageTransformer.toNotification(cachedNotification);
+ private DOMNotification transformNotification(final NetconfMessage cachedNotification) {
+ final DOMNotification parsedNotification = messageTransformer.toNotification(cachedNotification);
Preconditions.checkNotNull(parsedNotification, "%s: Unable to parse received notification: %s", id, cachedNotification);
return parsedNotification;
}
queue.add(notification);
}
- private synchronized void passNotification(final ContainerNode parsedNotification) {
+ private synchronized void passNotification(final DOMNotification parsedNotification) {
logger.debug("{}: Forwarding notification {}", id, parsedNotification);
if(filter == null || filter.filterNotification(parsedNotification).isPresent()) {
static interface NotificationFilter {
- Optional<NormalizedNode<?, ?>> filterNotification(NormalizedNode<?, ?> notification);
+ Optional<DOMNotification> filterNotification(DOMNotification notification);
}
}
package org.opendaylight.controller.sal.connect.netconf.sal;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
-
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
class NetconfDeviceNotificationService implements DOMNotificationService {
// Notification publish is very simple and hijacks the thread of the caller
// TODO shouldnt we reuse the implementation for notification router from sal-broker-impl ?
- public synchronized void publishNotification(final ContainerNode notification) {
- final SchemaPath schemaPath = toPath(notification.getNodeType());
- for (final DOMNotificationListener domNotificationListener : listeners.get(schemaPath)) {
- domNotificationListener.onNotification(new DOMNotification() {
- @Nonnull
- @Override
- public SchemaPath getType() {
- return schemaPath;
- }
-
- @Nonnull
- @Override
- public ContainerNode getBody() {
- return notification;
- }
- });
+ public synchronized void publishNotification(final DOMNotification notification) {
+ for (final DOMNotificationListener domNotificationListener : listeners.get(notification.getType())) {
+ domNotificationListener.onNotification(notification);
}
}
import java.util.Collections;
import java.util.List;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler;
import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
import org.opendaylight.controller.sal.core.api.Broker;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
}
@Override
- public synchronized void onNotification(final ContainerNode domNotification) {
+ public synchronized void onNotification(final DOMNotification domNotification) {
salProvider.getMountInstance().publish(domNotification);
}
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
import org.opendaylight.controller.sal.core.api.Broker;
import org.opendaylight.controller.sal.core.api.Provider;
import org.opendaylight.yangtools.concepts.ObjectRegistration;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
mountService = null;
}
- public synchronized void publish(final ContainerNode domNotification) {
+ public synchronized void publish(final DOMNotification domNotification) {
Preconditions.checkNotNull(notificationService, "Device not set up yet, cannot handle notification {}", domNotification);
notificationService.publishNotification(domNotification);
}
*/
package org.opendaylight.controller.sal.connect.netconf.schema.mapping;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.EVENT_TIME;
import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RPC_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_URI;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import java.util.Map;
-import java.util.NoSuchElementException;
import java.util.Set;
+import javax.annotation.Nonnull;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.dom.DOMResult;
+import org.opendaylight.controller.md.sal.dom.api.DOMEvent;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.notifications.NetconfNotification;
import org.opendaylight.controller.netconf.util.OrderedNormalizedNodeWriter;
import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
}
@Override
- public synchronized ContainerNode toNotification(final NetconfMessage message) {
- final XmlElement stripped = stripNotification(message);
+ public synchronized DOMNotification toNotification(final NetconfMessage message) {
+ final Map.Entry<Date, XmlElement> stripped = stripNotification(message);
final QName notificationNoRev;
try {
// How to construct QName with no revision ?
- notificationNoRev = QName.cachedReference(QName.create(stripped.getNamespace(), "0000-00-00", stripped.getName()).withoutRevision());
+ notificationNoRev = QName.cachedReference(QName.create(stripped.getValue().getNamespace(), "0000-00-00", stripped.getValue().getName()).withoutRevision());
} catch (final MissingNameSpaceException e) {
throw new IllegalArgumentException("Unable to parse notification " + message + ", cannot find namespace", e);
}
// We wrap the notification as a container node in order to reuse the parsers and builders for container node
final ContainerSchemaNode notificationAsContainerSchemaNode = NetconfMessageTransformUtil.createSchemaForNotification(next);
- return parserFactory.getContainerNodeParser().parse(Collections.singleton(stripped.getDomElement()), notificationAsContainerSchemaNode);
+ final ContainerNode content = parserFactory.getContainerNodeParser().parse(Collections.singleton(stripped.getValue().getDomElement()),
+ notificationAsContainerSchemaNode);
+ return new NetconfDeviceNotification(content, stripped.getKey());
}
// FIXME move somewhere to util
- private static XmlElement stripNotification(final NetconfMessage message) {
+ private static Map.Entry<Date, XmlElement> stripNotification(final NetconfMessage message) {
final XmlElement xmlElement = XmlElement.fromDomDocument(message.getDocument());
final List<XmlElement> childElements = xmlElement.getChildElements();
Preconditions.checkArgument(childElements.size() == 2, "Unable to parse notification %s, unexpected format", message);
+
+ final XmlElement eventTimeElement;
+ final XmlElement notificationElement;
+
+ if (childElements.get(0).getName().equals(EVENT_TIME)) {
+ eventTimeElement = childElements.get(0);
+ notificationElement = childElements.get(1);
+ }
+ else if(childElements.get(1).getName().equals(EVENT_TIME)) {
+ eventTimeElement = childElements.get(1);
+ notificationElement = childElements.get(0);
+ } else {
+ throw new IllegalArgumentException("Notification payload does not contain " + EVENT_TIME + " " + message);
+ }
+
try {
- return Iterables.find(childElements, new Predicate<XmlElement>() {
- @Override
- public boolean apply(final XmlElement xmlElement) {
- return !xmlElement.getName().equals("eventTime");
- }
- });
- } catch (final NoSuchElementException e) {
- throw new IllegalArgumentException("Unable to parse notification " + message + ", cannot strip notification metadata", e);
+ return new AbstractMap.SimpleEntry<>(parseEventTime(eventTimeElement.getTextContent()), notificationElement);
+ } catch (NetconfDocumentedException e) {
+ throw new IllegalArgumentException("Notification payload does not contain " + EVENT_TIME + " " + message);
+ }
+ }
+
+ private static Date parseEventTime(final String eventTime) {
+ try {
+ return new SimpleDateFormat(NetconfNotification.RFC3339_DATE_FORMAT_BLUEPRINT).parse(eventTime);
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Unable to parse event time from " + eventTime, e);
}
}
return new DefaultDOMRpcResult(normalizedNode);
}
+ private static class NetconfDeviceNotification implements DOMNotification, DOMEvent {
+ private final ContainerNode content;
+ private final SchemaPath schemaPath;
+ private final Date eventTime;
+
+ NetconfDeviceNotification(final ContainerNode content, final Date eventTime) {
+ this.content = content;
+ this.eventTime = eventTime;
+ this.schemaPath = toPath(content.getNodeType());
+ }
+
+ @Nonnull
+ @Override
+ public SchemaPath getType() {
+ return schemaPath;
+
+ }
+
+ @Nonnull
+ @Override
+ public ContainerNode getBody() {
+ return content;
+ }
+
+ @Override
+ public Date getEventTime() {
+ return eventTime;
+ }
+ }
}
// Blank document used for creation of new DOM nodes
private static final Document BLANK_DOCUMENT = XmlUtil.newDocument();
+ public static final String EVENT_TIME = "eventTime";
private NetconfMessageTransformUtil() {}
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
device.onNotification(notification);
device.onNotification(notification);
- verify(facade, times(0)).onNotification(any(ContainerNode.class));
+ verify(facade, times(0)).onNotification(any(DOMNotification.class));
final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
Lists.newArrayList(TEST_CAPABILITY));
device.handleSalInitializationSuccess(NetconfToNotificationTest.getNotificationSchemaContext(getClass()), sessionCaps, deviceRpc);
- verify(facade, timeout(10000).times(2)).onNotification(any(ContainerNode.class));
+ verify(facade, timeout(10000).times(2)).onNotification(any(DOMNotification.class));
device.onNotification(notification);
- verify(facade, timeout(10000).times(3)).onNotification(any(ContainerNode.class));
+ verify(facade, timeout(10000).times(3)).onNotification(any(DOMNotification.class));
}
@Test
final RemoteDeviceHandler<NetconfSessionPreferences> remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class);
doNothing().when(remoteDeviceHandler).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class));
doNothing().when(remoteDeviceHandler).onDeviceDisconnected();
- doNothing().when(remoteDeviceHandler).onNotification(any(ContainerNode.class));
+ doNothing().when(remoteDeviceHandler).onNotification(any(DOMNotification.class));
return remoteDeviceHandler;
}
import com.google.common.collect.Iterables;
import java.io.InputStream;
+import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import org.junit.Before;
import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMEvent;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.notifications.NetconfNotification;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.opendaylight.controller.sal.connect.netconf.schema.mapping.NetconfMessageTransformer;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@Test
public void test() throws Exception {
- final ContainerNode root = messageTransformer.toNotification(userNotification);
+ final DOMNotification domNotification = messageTransformer.toNotification(userNotification);
+ final ContainerNode root = domNotification.getBody();
assertNotNull(root);
assertEquals(6, Iterables.size(root.getValue()));
assertEquals("user-visited-page", root.getNodeType().getLocalName());
+ assertEquals(new SimpleDateFormat(NetconfNotification.RFC3339_DATE_FORMAT_BLUEPRINT).parse("2007-07-08T00:01:00Z"),
+ ((DOMEvent) domNotification).getEventTime());
}
}
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
-<eventTime>2014-07-08T11:20:48UTC</eventTime>
+<eventTime>2007-07-08T00:01:00Z</eventTime>
<user-visited-page xmlns="org:opendaylight:notification:test:ns:yang:user-notification">
<ui:incoming-user xmlns:ui="org:opendaylight:notification:test:ns:yang:user-notification">ui:public-user</ui:incoming-user>
<ip-address>172.23.29.104</ip-address>
import com.google.common.base.Optional;
import jline.console.completer.Completer;
import jline.console.completer.NullCompleter;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher;
import org.opendaylight.controller.netconf.cli.io.ConsoleContext;
import org.opendaylight.controller.netconf.cli.io.ConsoleIO;
import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler;
import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
/**
}
@Override
- public void onNotification(ContainerNode domNotification) {
+ public void onNotification(DOMNotification domNotification) {
}