import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import io.netty.channel.Channel;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext;
import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreService;
import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider;
+import org.opendaylight.controller.netconf.impl.NetconfServerSession;
+import org.opendaylight.controller.netconf.impl.NetconfServerSessionListener;
import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession;
import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory;
import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter;
import org.opendaylight.controller.netconf.mapping.api.HandlingPriority;
import org.opendaylight.controller.netconf.mapping.api.NetconfOperation;
import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
private void closeSession() throws NetconfDocumentedException, ParserConfigurationException, SAXException,
IOException {
+ final Channel channel = mock(Channel.class);
+ doReturn("channel").when(channel).toString();
+ final NetconfServerSessionListener listener = mock(NetconfServerSessionListener.class);
+ final NetconfServerSession session =
+ new NetconfServerSession(listener, channel, 1L,
+ NetconfHelloMessageAdditionalHeader.fromString("[netconf;10.12.0.102:48528;ssh;;;;;;]"));
DefaultCloseSession closeOp = new DefaultCloseSession(NETCONF_SESSION_ID, sessionCloseable);
+ closeOp.setNetconfSession(session);
executeOp(closeOp, "netconfMessages/closeSession.xml");
}
import com.google.common.base.Preconditions;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import java.text.SimpleDateFormat;
private Date loginTime;
private long inRpcSuccess, inRpcFail, outRpcError;
+ private volatile boolean delayedClose;
public NetconfServerSession(final NetconfServerSessionListener sessionListener, final Channel channel, final long sessionId,
final NetconfHelloMessageAdditionalHeader header) {
super.sessionUp();
}
+ /**
+ * Close this session after next message is sent.
+ * Suitable for close rpc that needs to send ok response before the session is closed.
+ */
+ public void delayedClose() {
+ this.delayedClose = true;
+ }
+
+ @Override
+ public ChannelFuture sendMessage(final NetconfMessage netconfMessage) {
+ final ChannelFuture channelFuture = super.sendMessage(netconfMessage);
+ // delayed close was set, close after the message was sent
+ if(delayedClose) {
+ channelFuture.addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(final ChannelFuture future) throws Exception {
+ close();
+ }
+ });
+ }
+ return channelFuture;
+ }
+
public void onIncommingRpcSuccess() {
inRpcSuccess++;
}
import org.opendaylight.controller.netconf.api.NetconfTerminationReason;
import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
-import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession;
import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter;
import org.opendaylight.controller.netconf.util.messages.SendErrorExceptionUtil;
-import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
session);
LOG.debug("Responding with message {}", message);
session.sendMessage(message);
-
- if (isCloseSession(netconfMessage)) {
- closeNetconfSession(session);
- }
-
} catch (final RuntimeException e) {
// TODO: should send generic error or close session?
LOG.error("Unexpected exception", e);
}
}
- private void closeNetconfSession(final NetconfServerSession session) {
- // destroy NetconfOperationService
- session.close();
- LOG.info("Session {} closed successfully", session.getSessionId());
- }
-
-
-
private NetconfMessage processDocument(final NetconfMessage netconfMessage, final NetconfServerSession session)
throws NetconfDocumentedException {
ImmutableMap.of(NetconfDocumentedException.ErrorTag.missing_attribute.toString(),
XmlNetconfConstants.MESSAGE_ID));
}
-
- private static boolean isCloseSession(final NetconfMessage incomingDocument) {
- final Document document = incomingDocument.getDocument();
- XmlElement rpcElement = XmlElement.fromDomDocument(document);
- if (rpcElement.getOnlyChildElementOptionally(DefaultCloseSession.CLOSE_SESSION).isPresent()) {
- return true;
- }
-
- return false;
- }
}
package org.opendaylight.controller.netconf.impl.mapping.operations;
import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
import java.util.Collections;
import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.controller.netconf.impl.NetconfServerSession;
import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-public class DefaultCloseSession extends AbstractSingletonNetconfOperation {
+public class DefaultCloseSession extends AbstractSingletonNetconfOperation implements DefaultNetconfOperation {
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultCloseSession.class);
+
public static final String CLOSE_SESSION = "close-session";
+
private final AutoCloseable sessionResources;
+ private NetconfServerSession session;
public DefaultCloseSession(String netconfSessionIdForReporting, AutoCloseable sessionResources) {
super(netconfSessionIdForReporting);
throws NetconfDocumentedException {
try {
sessionResources.close();
+ Preconditions.checkNotNull(session, "Session was not set").delayedClose();
+ LOG.info("Session {} closing", session.getSessionId());
} catch (Exception e) {
throw new NetconfDocumentedException("Unable to properly close session "
+ getNetconfSessionIdForReporting(), NetconfDocumentedException.ErrorType.application,
}
return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.<String>absent());
}
+
+ @Override
+ public void setNetconfSession(final NetconfServerSession s) {
+ this.session = s;
+ }
}
package org.opendaylight.controller.netconf.impl.mapping.operations;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.util.concurrent.GenericFutureListener;
import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.api.NetconfTerminationReason;
+import org.opendaylight.controller.netconf.impl.NetconfServerSession;
+import org.opendaylight.controller.netconf.impl.NetconfServerSessionListener;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.w3c.dom.Document;
public class DefaultCloseSessionTest {
+
@Test
public void testDefaultCloseSession() throws Exception {
AutoCloseable res = mock(AutoCloseable.class);
doNothing().when(res).close();
- DefaultCloseSession session = new DefaultCloseSession("", res);
+ DefaultCloseSession close = new DefaultCloseSession("", res);
Document doc = XmlUtil.newDocument();
XmlElement elem = XmlElement.fromDomElement(XmlUtil.readXmlToElement("<elem/>"));
- session.handleWithNoSubsequentOperations(doc, elem);
+ final Channel channel = mock(Channel.class);
+ doReturn("channel").when(channel).toString();
+ doReturn(mock(ChannelFuture.class)).when(channel).close();
+
+ final ChannelFuture sendFuture = mock(ChannelFuture.class);
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(final InvocationOnMock invocation) throws Throwable {
+ ((GenericFutureListener) invocation.getArguments()[0]).operationComplete(sendFuture);
+ return null;
+ }
+ }).when(sendFuture).addListener(any(GenericFutureListener.class));
+ doReturn(sendFuture).when(channel).writeAndFlush(anyObject());
+ final NetconfServerSessionListener listener = mock(NetconfServerSessionListener.class);
+ doNothing().when(listener).onSessionTerminated(any(NetconfServerSession.class), any(NetconfTerminationReason.class));
+ final NetconfServerSession session =
+ new NetconfServerSession(listener, channel, 1L,
+ NetconfHelloMessageAdditionalHeader.fromString("[netconf;10.12.0.102:48528;ssh;;;;;;]"));
+ close.setNetconfSession(session);
+ close.handleWithNoSubsequentOperations(doc, elem);
+ // Fake close response to trigger delayed close
+ session.sendMessage(new NetconfMessage(XmlUtil.readXmlToDocument("<rpc-reply message-id=\"101\"\n" +
+ "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
+ "<ok/>\n" +
+ "</rpc-reply>")));
+ verify(channel).close();
+ verify(listener).onSessionTerminated(any(NetconfServerSession.class), any(NetconfTerminationReason.class));
}
@Test(expected = NetconfDocumentedException.class)
/**
*
* @param tagName tag name without prefix
- * @return
+ * @return List of child elements
*/
public List<XmlElement> getChildElements(final String tagName) {
return getChildElementsInternal(new ElementFilteringStrategy() {
}
public Optional<XmlElement> getOnlyChildElementOptionally(String childName) {
- try {
- return Optional.of(getOnlyChildElement(childName));
- } catch (Exception e) {
+ List<XmlElement> nameElements = getChildElements(childName);
+ if (nameElements.size() != 1) {
return Optional.absent();
}
+ return Optional.of(nameElements.get(0));
}
- public Optional<XmlElement> getOnlyChildElementOptionally(String childName, String namespace) {
- try {
- return Optional.of(getOnlyChildElement(childName, namespace));
- } catch (Exception e) {
+ public Optional<XmlElement> getOnlyChildElementOptionally(final String childName, final String namespace) {
+ List<XmlElement> children = getChildElementsWithinNamespace(namespace);
+ children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
+ @Override
+ public boolean apply(XmlElement xmlElement) {
+ return xmlElement.getName().equals(childName);
+ }
+ }));
+ if (children.size() != 1){
return Optional.absent();
}
+ return Optional.of(children.get(0));
}
public XmlElement getOnlyChildElementWithSameNamespace(String childName) throws NetconfDocumentedException {
return getOnlyChildElement(childName, getNamespace());
}
- public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(String childName) {
- try {
- return Optional.of(getOnlyChildElement(childName, getNamespace()));
- } catch (Exception e) {
- return Optional.absent();
+ public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally(final String childName) {
+ Optional<String> namespace = getNamespaceOptionally();
+ if (namespace.isPresent()) {
+ List<XmlElement> children = getChildElementsWithinNamespace(namespace.get());
+ children = Lists.newArrayList(Collections2.filter(children, new Predicate<XmlElement>() {
+ @Override
+ public boolean apply(XmlElement xmlElement) {
+ return xmlElement.getName().equals(childName);
+ }
+ }));
+ if (children.size() != 1){
+ return Optional.absent();
+ }
+ return Optional.of(children.get(0));
}
+ return Optional.absent();
}
public XmlElement getOnlyChildElementWithSameNamespace() throws NetconfDocumentedException {
}
public Optional<XmlElement> getOnlyChildElementWithSameNamespaceOptionally() {
- try {
- XmlElement childElement = getOnlyChildElement();
- childElement.checkNamespace(getNamespace());
- return Optional.of(childElement);
- } catch (Exception e) {
- return Optional.absent();
+ Optional<XmlElement> child = getOnlyChildElementOptionally();
+ if (child.isPresent()
+ && child.get().getNamespaceOptionally().isPresent()
+ && getNamespaceOptionally().isPresent()
+ && getNamespaceOptionally().get().equals(child.get().getNamespaceOptionally().get())) {
+ return child;
}
+ return Optional.absent();
}
public XmlElement getOnlyChildElement(final String childName, String namespace) throws NetconfDocumentedException {
return children.get(0);
}
+ public Optional<XmlElement> getOnlyChildElementOptionally() {
+ List<XmlElement> children = getChildElements();
+ if (children.size() != 1) {
+ return Optional.absent();
+ }
+ return Optional.of(children.get(0));
+ }
+
public String getTextContent() throws NetconfDocumentedException {
NodeList childNodes = element.getChildNodes();
if (childNodes.getLength() == 0) {
return attribute;
}
+ public Optional<String> getNamespaceAttributeOptionally(){
+ String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY);
+ if (attribute == null || attribute.equals(DEFAULT_NAMESPACE_PREFIX)){
+ return Optional.absent();
+ }
+ return Optional.of(attribute);
+ }
+
public Optional<String> getNamespaceOptionally() {
String namespaceURI = element.getNamespaceURI();
if (Strings.isNullOrEmpty(namespaceURI)) {
public String getNamespace() throws MissingNameSpaceException {
Optional<String> namespaceURI = getNamespaceOptionally();
- if (namespaceURI.isPresent() == false){
+ if (!namespaceURI.isPresent()){
throw new MissingNameSpaceException(String.format("No namespace defined for %s", this),
NetconfDocumentedException.ErrorType.application,
NetconfDocumentedException.ErrorTag.operation_failed,
XmlElement that = (XmlElement) o;
- if (!element.isEqualNode(that.element)) {
- return false;
- }
+ return element.isEqualNode(that.element);
- return true;
}
@Override
}
public boolean hasNamespace() {
- try {
- getNamespaceAttribute();
- } catch (MissingNameSpaceException e) {
- try {
- getNamespace();
- } catch (MissingNameSpaceException e1) {
+ if (!getNamespaceAttributeOptionally().isPresent()) {
+ if (!getNamespaceOptionally().isPresent()) {
return false;
}
- return true;
}
return true;
}