Merge "SSH netty handler"
authorGiovanni Meo <gmeo@cisco.com>
Thu, 7 Nov 2013 15:38:56 +0000 (15:38 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 7 Nov 2013 15:38:56 +0000 (15:38 +0000)
15 files changed:
opendaylight/commons/opendaylight/pom.xml
opendaylight/distribution/opendaylight/pom.xml
opendaylight/netconf/netconf-util/pom.xml
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/AuthenticationHandler.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/Invoker.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshSession.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelInputStream.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelOutputStream.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocket.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocketException.java [new file with mode: 0644]
opendaylight/netconf/pom.xml

index f7b2a01eb845ab7983f39c3fad5bc6e30843559e..8ff25c35def4f93e9b0d1a98b81f21e5673b96bc 100644 (file)
         <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
         <version>7.0.42</version>
       </dependency>
+      <dependency>
+        <groupId>org.opendaylight.controller.thirdparty</groupId>
+        <artifactId>ganymed</artifactId>
+        <version>1.0-SNAPSHOT</version>
+      </dependency>
       <!-- yang model dependencies -->
       <dependency>
        <groupId>org.opendaylight.yangtools.model</groupId>
index 9b07f75c6dc315ce97cfec342599f4136b126e9d..a0d7162b3f524630ecbe944eebe0551abfa07086 100644 (file)
            <version>2.4</version>
          </dependency>
 
-           <dependency>
+         <dependency>
           <groupId>org.opendaylight.yangtools.thirdparty</groupId>
           <artifactId>antlr4-runtime-osgi-nohead</artifactId>
           <version>4.0</version>
           <artifactId>yang-model-api</artifactId>
          </dependency>
 
-          <dependency>
-           <groupId>org.opendaylight.yangtools.model</groupId>
-           <artifactId>yang-ext</artifactId>
-          </dependency>
+         <dependency>
+          <groupId>org.opendaylight.yangtools.model</groupId>
+          <artifactId>yang-ext</artifactId>
+         </dependency>
 
+        <dependency>
+         <groupId>org.opendaylight.controller.thirdparty</groupId>
+         <artifactId>ganymed</artifactId>
+        </dependency>
       </dependencies>
     </profile>
   </profiles>
index 20603c477462ac31e38ee00d75b15ddd91f48e36..d6bf62413a569949da0ca09de44d5678f3c9a4da 100644 (file)
             <artifactId>netty-handler</artifactId>
             <version>${netconf.netty.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller.thirdparty</groupId>
+            <artifactId>ganymed</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
@@ -72,6 +76,7 @@
                             org.opendaylight.controller.config.stat,
                             com.google.common.base,
                             com.google.common.collect,
+                            ch.ethz.ssh2,
                             io.netty.buffer,
                             io.netty.channel,
                             io.netty.channel.socket,
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java
new file mode 100644 (file)
index 0000000..b911989
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh;
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+import java.io.IOException;
+import java.net.SocketAddress;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker;
+import org.opendaylight.controller.netconf.util.handler.ssh.client.SshClient;
+import org.opendaylight.controller.netconf.util.handler.ssh.client.SshClientAdapter;
+import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocket;
+
+/**
+ * Netty SSH handler class. Acts as interface between Netty and SSH library. All standard Netty message handling
+ * stops at instance of this class. All downstream events are handed of to wrapped {@link org.opendaylight.controller.netconf.util.handler.ssh.client.SshClientAdapter};
+ */
+public class SshHandler extends ChannelOutboundHandlerAdapter {
+    private final VirtualSocket virtualSocket = new VirtualSocket();
+    private final SshClientAdapter sshClientAdapter;
+
+    public SshHandler(AuthenticationHandler authenticationHandler, Invoker invoker) throws IOException {
+        SshClient sshClient = new SshClient(virtualSocket, authenticationHandler);
+        this.sshClientAdapter = new SshClientAdapter(sshClient, invoker);
+    }
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx){
+        if (ctx.channel().pipeline().get("socket") == null) {
+            ctx.channel().pipeline().addFirst("socket", virtualSocket);
+        }
+    }
+
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        if (ctx.channel().pipeline().get("socket") != null) {
+            ctx.channel().pipeline().remove("socket");
+        }
+    }
+
+    @Override
+    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+        this.sshClientAdapter.write((String) msg);
+    }
+
+    @Override
+    public void connect(final ChannelHandlerContext ctx,
+                        SocketAddress remoteAddress,
+                        SocketAddress localAddress,
+                        ChannelPromise promise) throws Exception {
+        ctx.connect(remoteAddress, localAddress, promise);
+
+        promise.addListener(new ChannelFutureListener() {
+            public void operationComplete(ChannelFuture channelFuture) throws Exception {
+                sshClientAdapter.start(ctx);
+            }}
+        );
+    }
+
+    @Override
+    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+        sshClientAdapter.stop(promise);
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/AuthenticationHandler.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/AuthenticationHandler.java
new file mode 100644 (file)
index 0000000..a0e82f8
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.authentication;
+
+import ch.ethz.ssh2.Connection;
+
+import java.io.IOException;
+
+/**
+ * Class providing authentication facility to SSH handler.
+ */
+public abstract class AuthenticationHandler {
+    public abstract void authenticate(Connection connection) throws IOException;
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java
new file mode 100644 (file)
index 0000000..bb0d378
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.authentication;
+
+import ch.ethz.ssh2.Connection;
+
+import java.io.IOException;
+
+/**
+ * Class Providing username/password authentication option to {@link org.opendaylight.controller.netconf.util.handler.ssh.SshHandler}
+ */
+public class LoginPassword extends AuthenticationHandler {
+    private final String username;
+    private final String password;
+
+    public LoginPassword(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public void authenticate(Connection connection) throws IOException {
+        boolean isAuthenticated = connection.authenticateWithPassword(username, password);
+
+        if (isAuthenticated == false) throw new IOException("Authentication failed.");
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/Invoker.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/Invoker.java
new file mode 100644 (file)
index 0000000..12d1129
--- /dev/null
@@ -0,0 +1,36 @@
+package org.opendaylight.controller.netconf.util.handler.ssh.client;
+
+import java.io.IOException;
+
+/**
+ * Abstract class providing mechanism of invoking various SSH level services.
+ * Class is not allowed to be extended, as it provides its own implementations via instance initiators.
+ */
+public abstract class Invoker {
+    private boolean invoked = false;
+
+    private Invoker(){}
+
+    protected boolean isInvoked() {
+        return invoked;
+    }
+
+    abstract void invoke(SshSession session) throws IOException;
+
+    /**
+     * Invoker implementation to invokes subsystem SSH service.
+     *
+     * @param subsystem
+     * @return
+     */
+    public static Invoker subsystem(final String subsystem) {
+        return new Invoker() {
+            @Override
+            void invoke(SshSession session) throws IOException {
+                if (isInvoked() == true) throw new IllegalStateException("Already invoked.");
+
+                session.startSubSystem(subsystem);
+            }
+        };
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java
new file mode 100644 (file)
index 0000000..c43aa6f
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.client;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import ch.ethz.ssh2.channel.Channel;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocket;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Wrapper class around GANYMED SSH java library.
+ */
+public class SshClient {
+    private final VirtualSocket socket;
+    private final Map<Integer, SshSession> openSessions = new HashMap();
+    private final AuthenticationHandler authenticationHandler;
+    private Connection connection;
+
+    public SshClient(VirtualSocket socket,
+                     AuthenticationHandler authenticationHandler) throws IOException {
+        this.socket = socket;
+        this.authenticationHandler = authenticationHandler;
+    }
+
+    public SshSession openSession() throws IOException {
+        if(connection == null) connect();
+
+        Session session =  connection.openSession();
+        SshSession sshSession = new SshSession(session);
+        openSessions.put(openSessions.size(), sshSession);
+
+        return sshSession;
+    }
+
+    private void connect() throws IOException {
+        connection = new Connection(socket);
+        connection.connect();
+        authenticationHandler.authenticate(connection);
+    }
+
+    public void closeSession(SshSession session) {
+        if(   session.getState() == Channel.STATE_OPEN
+           || session.getState() == Channel.STATE_OPENING) {
+            session.session.close();
+        }
+    }
+
+    public void close() {
+        for(SshSession session : openSessions.values()) closeSession(session);
+
+        openSessions.clear();
+
+        if(connection != null) connection.close();
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java
new file mode 100644 (file)
index 0000000..a50462e
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.client;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocketException;
+
+/**
+ * Worker thread class. Handles all downstream and upstream events in SSH Netty pipeline.
+ */
+public class SshClientAdapter implements Runnable {
+    private final SshClient sshClient;
+    private final Invoker invoker;
+
+    private SshSession session;
+    private InputStream stdOut;
+    private InputStream stdErr;
+    private OutputStream stdIn;
+
+    private ChannelHandlerContext ctx;
+    private ChannelPromise disconnectPromise;
+
+    private final AtomicBoolean stopRequested = new AtomicBoolean(false);
+
+    private final Object lock = new Object();
+
+    public SshClientAdapter(SshClient sshClient,
+                            Invoker invoker) {
+        this.sshClient = sshClient;
+        this.invoker = invoker;
+    }
+
+    public void run() {
+        try {
+            session = sshClient.openSession();
+            invoker.invoke(session);
+
+            stdOut = session.getStdout();
+            stdErr = session.getStderr();
+
+            synchronized(lock) {
+                stdIn = session.getStdin();
+            }
+
+            while (stopRequested.get() == false) {
+                byte[] readBuff = new byte[1024];
+                int c = stdOut.read(readBuff);
+
+                byte[] tranBuff = new byte[c];
+                System.arraycopy(readBuff, 0, tranBuff, 0, c);
+
+                ByteBuf byteBuf = Unpooled.buffer(c);
+                byteBuf.writeBytes(tranBuff);
+                ctx.fireChannelRead(byteBuf);
+            }
+
+        } catch (VirtualSocketException e) {
+            // Netty closed connection prematurely.
+            // Just pass and move on.
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            sshClient.close();
+
+            synchronized (lock) {
+                if(disconnectPromise != null) ctx.disconnect(disconnectPromise);
+            }
+        }
+    }
+
+    // TODO: needs rework to match netconf framer API.
+    public void write(String message) throws IOException {
+        synchronized (lock) {
+            if (stdIn == null) throw new IllegalStateException("StdIn not available");
+        }
+        stdIn.write(message.getBytes());
+        stdIn.flush();
+    }
+
+    public void stop(ChannelPromise promise) {
+        synchronized (lock) {
+            stopRequested.set(true);
+            disconnectPromise = promise;
+        }
+    }
+
+    public void start(ChannelHandlerContext ctx) {
+        if(this.ctx != null) return; // context is already associated.
+
+        this.ctx = ctx;
+        new Thread(this).start();
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshSession.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshSession.java
new file mode 100644 (file)
index 0000000..df400aa
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.client;
+
+import ch.ethz.ssh2.Session;
+import ch.ethz.ssh2.StreamGobbler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Wrapper class for proprietary SSH sessions implementations
+ */
+public class SshSession {
+    final Session session;
+
+    public SshSession(Session session) {
+        this.session = session;
+    }
+
+    public void execCommand(String cmd) throws IOException {
+        session.execCommand(cmd);
+    }
+
+    public void execCommand(String cmd, String charsetName) throws IOException {
+        session.execCommand(cmd, charsetName);
+    }
+
+    public void startShell() throws IOException {
+        session.startShell();
+    }
+
+    public void startSubSystem(String name) throws IOException {
+        session.startSubSystem(name);
+    }
+
+    public int getState() {
+        return session.getState();
+    }
+
+    public InputStream getStdout() {
+        return new StreamGobbler(session.getStdout());
+    }
+
+    public InputStream getStderr() {
+        return session.getStderr();
+    }
+
+    public OutputStream getStdin() {
+        return session.getStdin();
+    }
+
+    public int waitUntilDataAvailable(long timeout) throws IOException {
+        return session.waitUntilDataAvailable(timeout);
+    }
+
+    public int waitForCondition(int condition_set, long timeout) {
+        return session.waitForCondition(condition_set, timeout);
+    }
+
+    public Integer getExitStatus() {
+        return session.getExitStatus();
+    }
+
+    public String getExitSignal() {
+        return session.getExitSignal();
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelInputStream.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelInputStream.java
new file mode 100644 (file)
index 0000000..07c81b0
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.virtualsocket;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Class provides {@link InputStream} functionality to users of virtual socket.
+ */
+public class ChannelInputStream extends InputStream implements ChannelInboundHandler {
+    private final Object lock = new Object();
+    private final ByteBuf bb = Unpooled.buffer();
+
+    @Override
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        } else if (off < 0 || len < 0 || len > b.length - off) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return 0;
+        }
+
+        int bytesRead = 1;
+        synchronized (lock) {
+            int c = read();
+
+            b[off] = (byte)c;
+
+            if(this.bb.readableBytes() == 0) return bytesRead;
+
+            int ltr = len-1;
+            ltr = (ltr <= bb.readableBytes()) ? ltr : bb.readableBytes();
+
+            bb.readBytes(b, 1, ltr);
+            bytesRead += ltr;
+        }
+        return bytesRead;
+    }
+
+    @Override
+    public int read() throws IOException {
+        synchronized (lock) {
+            while (this.bb.readableBytes() == 0) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            return this.bb.readByte() & 0xFF;
+        }
+    }
+
+    @Override
+    public int available() throws IOException {
+        synchronized (lock) {
+            return this.bb.readableBytes();
+        }
+    }
+
+    public void channelRegistered(ChannelHandlerContext ctx)
+            throws Exception {
+        ctx.fireChannelRegistered();
+    }
+
+    public void channelUnregistered(ChannelHandlerContext ctx)
+            throws Exception {
+        ctx.fireChannelUnregistered();
+    }
+
+    public void channelActive(ChannelHandlerContext ctx)
+            throws Exception {
+        ctx.fireChannelActive();
+    }
+
+    public void channelInactive(ChannelHandlerContext ctx)
+            throws Exception {
+        ctx.fireChannelInactive();
+    }
+
+    public void channelRead(ChannelHandlerContext ctx, Object o)
+            throws Exception {
+        synchronized(lock) {
+            this.bb.discardReadBytes();
+            this.bb.writeBytes((ByteBuf) o);
+            lock.notifyAll();
+        }
+    }
+
+    public void channelReadComplete(ChannelHandlerContext ctx)
+            throws Exception {
+        ctx.fireChannelReadComplete();
+    }
+
+    public void userEventTriggered(ChannelHandlerContext ctx, Object o)
+            throws Exception {
+        ctx.fireUserEventTriggered(o);
+    }
+
+    public void channelWritabilityChanged(ChannelHandlerContext ctx)
+            throws Exception {
+        ctx.fireChannelWritabilityChanged();
+    }
+
+    public void handlerAdded(ChannelHandlerContext ctx)
+            throws Exception {
+    }
+
+    public void handlerRemoved(ChannelHandlerContext ctx)
+            throws Exception {
+    }
+
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable)
+            throws Exception {
+        ctx.fireExceptionCaught(throwable);
+    }
+}
+
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelOutputStream.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/ChannelOutputStream.java
new file mode 100644 (file)
index 0000000..b1314a6
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.virtualsocket;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOutboundHandler;
+import io.netty.channel.ChannelPromise;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.SocketAddress;
+
+/**
+ * Class provides {@link OutputStream) functionality to users of virtual socket.
+ */
+public class ChannelOutputStream extends OutputStream implements ChannelOutboundHandler {
+    private final Object lock = new Object();
+    private ByteBuf buff = Unpooled.buffer();
+    private ChannelHandlerContext ctx;
+
+    @Override
+    public void flush() throws IOException {
+        synchronized(lock) {
+            ctx.writeAndFlush(buff).awaitUninterruptibly();
+            buff = Unpooled.buffer();
+        }
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        synchronized(lock) {
+            buff.writeByte(b);
+        }
+    }
+
+    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
+                     ChannelPromise promise) throws Exception {
+        ctx.bind(localAddress, promise);
+    }
+
+    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
+                        SocketAddress localAddress, ChannelPromise promise)
+            throws Exception {
+        this.ctx = ctx;
+        ctx.connect(remoteAddress, localAddress, promise);
+    }
+
+    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
+            throws Exception {
+        ctx.disconnect(promise);
+    }
+
+    public void close(ChannelHandlerContext ctx, ChannelPromise promise)
+            throws Exception {
+        ctx.close(promise);
+    }
+
+    public void deregister(ChannelHandlerContext ctx, ChannelPromise channelPromise)
+            throws Exception {
+        ctx.deregister(channelPromise);
+    }
+
+    public void read(ChannelHandlerContext ctx)
+            throws Exception {
+        ctx.read();
+    }
+
+    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
+            throws Exception {
+        // pass
+    }
+
+    public void flush(ChannelHandlerContext ctx)
+            throws Exception {
+        // pass
+    }
+
+    public void handlerAdded(ChannelHandlerContext ctx)
+            throws Exception {
+    }
+
+    public void handlerRemoved(ChannelHandlerContext ctx)
+            throws Exception {
+    }
+
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
+            throws Exception {
+        ctx.fireExceptionCaught(cause);
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocket.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocket.java
new file mode 100644 (file)
index 0000000..1011ca1
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.virtualsocket;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+
+/**
+ * Handler class providing Socket functionality to OIO client application. By using VirtualSocket user can
+ * use OIO application in asynchronous environment and NIO EventLoop. Using VirtualSocket OIO applications
+ * are able to use full potential of NIO environment.
+ */
+public class VirtualSocket extends Socket implements ChannelHandler {
+    private final ChannelInputStream chis = new ChannelInputStream();
+    private final ChannelOutputStream chos = new ChannelOutputStream();
+    private ChannelHandlerContext ctx;
+
+
+    public InputStream getInputStream() {
+        return this.chis;
+    }
+
+    public OutputStream getOutputStream() {
+        return this.chos;
+    }
+
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        this.ctx = ctx;
+
+        if (ctx.channel().pipeline().get("outputStream") == null) {
+            ctx.channel().pipeline().addFirst("outputStream", chos);
+        }
+
+        if (ctx.channel().pipeline().get("inputStream") == null) {
+            ctx.channel().pipeline().addFirst("inputStream", chis);
+        }
+    }
+
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        if (ctx.channel().pipeline().get("outputStream") != null) {
+            ctx.channel().pipeline().remove("outputStream");
+        }
+
+        if (ctx.channel().pipeline().get("inputStream") != null) {
+            ctx.channel().pipeline().remove("inputStream");
+        }
+    }
+
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) throws Exception {
+        ctx.fireExceptionCaught(throwable);
+    }
+
+    public VirtualSocket() {super();}
+
+    @Override
+    public void connect(SocketAddress endpoint) throws IOException {}
+
+    @Override
+    public void connect(SocketAddress endpoint, int timeout) throws IOException {}
+
+    @Override
+    public void bind(SocketAddress bindpoint) throws IOException {}
+
+    @Override
+    public InetAddress getInetAddress() {
+        InetSocketAddress isa = getInetSocketAddress();
+
+        if (isa == null) throw new VirtualSocketException();
+
+        return getInetSocketAddress().getAddress();
+    }
+
+    @Override
+    public InetAddress getLocalAddress() {return null;}
+
+    @Override
+    public int getPort() {
+        return getInetSocketAddress().getPort();
+    }
+
+    private InetSocketAddress getInetSocketAddress() {
+        return (InetSocketAddress)getRemoteSocketAddress();
+    }
+
+    @Override
+    public int getLocalPort() {return -1;}
+
+    @Override
+    public SocketAddress getRemoteSocketAddress() {
+        return this.ctx.channel().remoteAddress();
+    }
+
+    @Override
+    public SocketAddress getLocalSocketAddress() {
+        return this.ctx.channel().localAddress();
+    }
+
+    @Override
+    public SocketChannel getChannel() {return null;}
+
+    @Override
+    public void setTcpNoDelay(boolean on) throws SocketException {}
+
+    @Override
+    public boolean getTcpNoDelay() throws SocketException {return false;}
+
+    @Override
+    public void setSoLinger(boolean on, int linger) throws SocketException {}
+
+    @Override
+    public int getSoLinger() throws SocketException {return -1;}
+
+    @Override
+    public void sendUrgentData(int data) throws IOException {}
+
+    @Override
+    public void setOOBInline(boolean on) throws SocketException {}
+
+    @Override
+    public boolean getOOBInline() throws SocketException {return false;}
+
+    @Override
+    public synchronized void setSoTimeout(int timeout) throws SocketException {}
+
+    @Override
+    public synchronized int getSoTimeout() throws SocketException {return -1;}
+
+    @Override
+    public synchronized void setSendBufferSize(int size) throws SocketException {}
+
+    @Override
+    public synchronized int getSendBufferSize() throws SocketException {return -1;}
+
+    @Override
+    public synchronized void setReceiveBufferSize(int size) throws SocketException {}
+
+    @Override
+    public synchronized int getReceiveBufferSize() throws SocketException {return -1;}
+
+    @Override
+    public void setKeepAlive(boolean on) throws SocketException {}
+
+    @Override
+    public boolean getKeepAlive() throws SocketException {return false;}
+
+    @Override
+    public void setTrafficClass(int tc) throws SocketException {}
+
+    @Override
+    public int getTrafficClass() throws SocketException {return -1;}
+
+    @Override
+    public void setReuseAddress(boolean on) throws SocketException {}
+
+    @Override
+    public boolean getReuseAddress() throws SocketException {return false;}
+
+    @Override
+    public synchronized void close() throws IOException {}
+
+    @Override
+    public void shutdownInput() throws IOException {}
+
+    @Override
+    public void shutdownOutput() throws IOException {}
+
+    @Override
+    public String toString() {
+        return "Virtual socket InetAdress["+getInetAddress()+"], Port["+getPort()+"]";
+    }
+
+    @Override
+    public boolean isConnected() {return false;}
+
+    @Override
+    public boolean isBound() {return false;}
+
+    @Override
+    public boolean isClosed() {return false;}
+
+    @Override
+    public boolean isInputShutdown() {return false;}
+
+    @Override
+    public boolean isOutputShutdown() {return false;}
+
+    @Override
+    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {}
+}
diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocketException.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/virtualsocket/VirtualSocketException.java
new file mode 100644 (file)
index 0000000..46fdbb8
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2013 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.util.handler.ssh.virtualsocket;
+
+/**
+ * Exception class which provides notification about exceptional situations at the virtual socket layer.
+ */
+public class VirtualSocketException extends RuntimeException {
+}
index 8f69f8dca066a949a306bd2e98b038bdeb392a5c..5447f7f5d057c71eba0e08369986c46c31013903 100644 (file)
@@ -26,6 +26,7 @@
         <module>config-persister-impl</module>
         <module>netconf-mapping-api</module>
         <module>netconf-client</module>
+        <module>../../third-party/ganymed</module>
     </modules>
 
     <profiles>