/*
* Copyright 2010 NCHOVY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.rpc.impl;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import org.krakenapps.api.KeyStoreManager;
import org.krakenapps.api.Script;
import org.krakenapps.api.ScriptArgument;
import org.krakenapps.api.ScriptContext;
import org.krakenapps.api.ScriptUsage;
import org.krakenapps.rpc.RpcBindingProperties;
import org.krakenapps.rpc.RpcBlockingTable;
import org.krakenapps.rpc.RpcConnection;
import org.krakenapps.rpc.RpcAgent;
import org.krakenapps.rpc.RpcConnectionProperties;
import org.krakenapps.rpc.RpcPeer;
import org.krakenapps.rpc.RpcPeerRegistry;
import org.krakenapps.rpc.RpcPeeringCallback;
import org.krakenapps.rpc.RpcServiceBinding;
import org.krakenapps.rpc.RpcSession;
import org.krakenapps.rpc.RpcTrustLevel;
import org.krakenapps.rpc.RpcWaitingCall;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RpcScript implements Script {
private final Logger logger = LoggerFactory.getLogger(RpcScript.class.getName());
private ScriptContext context;
private RpcAgent agent;
private KeyStoreManager keyStoreManager;
public RpcScript(RpcAgent agent, KeyStoreManager keyStoreManager) {
this.agent = agent;
this.keyStoreManager = keyStoreManager;
}
@Override
public void setScriptContext(ScriptContext context) {
this.context = context;
}
public void bindings(String[] args) {
context.println("Port Bindings");
context.println("---------------");
for (RpcBindingProperties props : agent.getBindings())
context.println(props);
}
@ScriptUsage(description = "open rpc port", arguments = {
@ScriptArgument(name = "port", type = "int", description = "rpc port"),
@ScriptArgument(name = "ip", type = "string", description = "bind address. '0.0.0.0' by default", optional = true) })
public void open(String[] args) {
RpcBindingProperties props = null;
try {
props = inputBindingProps(args);
agent.open(props);
context.println("opened rpc port: " + args[0]);
} catch (NumberFormatException e) {
context.println("invalid port number format");
} catch (Exception e) {
context.println(e.getMessage());
logger.error("kraken rpc: cannot bind " + props, e);
}
}
@ScriptUsage(description = "open rpc ssl port", arguments = {
@ScriptArgument(name = "port", type = "int", description = "rpc listening port for ssl"),
@ScriptArgument(name = "key alias", type = "string", description = "key alias. use 'keystore.list' command to list all registered key aliases. Keystore should contain public and private key pair. e.g. PKCS12 keystore"),
@ScriptArgument(name = "trust alias", type = "string", description = "trust alias. use 'keystore.list' command to list all registered key aliases. Trusted keystore should contain public key of certificate authority"),
@ScriptArgument(name = "ip", type = "string", description = "bind address. '0.0.0.0' by default", optional = true) })
public void openSsl(String[] args) {
RpcBindingProperties props = null;
try {
String keyAlias = args[1];
String trustAlias = args[2];
String ip = "0.0.0.0";
if (args.length > 3)
ip = args[3];
args[1] = ip;
args[2] = keyAlias;
args[3] = trustAlias;
props = inputBindingProps(args);
agent.open(props);
context.println("opened rpc (ssl) port: " + args[0]);
} catch (NumberFormatException e) {
context.println("invalid port number format");
} catch (Exception e) {
context.println(e.getMessage());
logger.error("kraken rpc: cannot bind " + props, e);
}
}
@ScriptUsage(description = "close rpc port or rpc ssl port", arguments = {
@ScriptArgument(name = "port", type = "int", description = "rpc port or rpc ssl port"),
@ScriptArgument(name = "ip", type = "string", description = "bind address. '0.0.0.0' by default", optional = true) })
public void close(String[] args) {
try {
RpcBindingProperties props = inputBindingProps(args);
if (!agent.getBindings().contains(props)) {
String endpoint = "0.0.0.0";
if (args.length > 1)
endpoint = args[1];
endpoint += ":" + args[0];
context.println(endpoint + " not opened");
return;
}
agent.close(props);
context.println("closed rpc port");
} catch (NumberFormatException e) {
context.println("invalid port number format");
} catch (Exception e) {
context.println(e.getMessage());
}
}
private RpcBindingProperties inputBindingProps(String[] args) {
int port = Integer.valueOf(args[0]);
if (port < 1 || port > 65535)
throw new IllegalArgumentException("port number should be 1~65535");
String addr = "0.0.0.0";
if (args.length > 1)
addr = args[1];
String keyAlias = null;
String trustAlias = null;
if (args.length >= 4) {
keyAlias = args[2];
trustAlias = args[3];
}
return new RpcBindingProperties(addr, port, keyAlias, trustAlias);
}
@ScriptUsage(description = "print guid of local agent")
public void guid(String[] args) {
context.println(agent.getGuid());
}
@ScriptUsage(description = "list all registered peers")
public void peers(String[] args) {
RpcPeerRegistry registry = agent.getPeerRegistry();
context.println("RPC Peers");
context.println("--------------");
for (String guid : registry.getPeerGuids()) {
context.println(guid);
}
}
@ScriptUsage(description = "register rpc peer", arguments = {
@ScriptArgument(name = "guid", type = "string", description = "the guid of peer"),
@ScriptArgument(name = "password", type = "string", description = "the password of peer"),
@ScriptArgument(name = "trust level", type = "int", description = "1(untrust), 2(low), 3(medium), 4(high)") })
public void registerPeer(String[] args) {
RpcPeerRegistry registry = agent.getPeerRegistry();
String guid = args[0];
String password = args[1];
int trustLevel = Integer.parseInt(args[2]);
if (trustLevel < 1 || trustLevel > 4) {
context.println("trust level should be between 1 and 4");
return;
}
RpcPeer peer = new RpcPeerConfig(guid, password, trustLevel);
try {
registry.register(peer);
context.println(peer.getGuid() + " registered");
} catch (IllegalStateException e) {
context.println("register failed: " + e.getMessage());
}
}
@ScriptUsage(description = "unregister rpc peer", arguments = { @ScriptArgument(name = "guid", type = "string", description = "peer's guid") })
public void unregisterPeer(String[] args) {
RpcPeerRegistry registry = agent.getPeerRegistry();
String guid = args[0];
try {
registry.unregister(guid);
context.println(guid + " unregistered");
} catch (IllegalStateException e) {
context.println("unregister failed: " + e.getMessage());
}
}
public void connections(String[] args) {
context.println("RPC Connections");
context.println("----------------");
for (RpcConnection connection : agent.getConnections()) {
context.println(connection.toString());
}
}
@ScriptUsage(description = "send peering request", arguments = {
@ScriptArgument(name = "cid", type = "int", description = "connection id"),
@ScriptArgument(name = "password", type = "string", description = "password", optional = true) })
public void requestPeering(String[] args) {
int id = Integer.parseInt(args[0]);
String password = null;
if (args.length > 1)
password = args[1];
RpcConnection conn = agent.findConnection(id);
if (conn == null) {
context.println("connection not found");
return;
}
PeeringResultPrinter p = new PeeringResultPrinter();
try {
p.lock.lock();
conn.requestPeering(p, password);
p.cond.await();
} catch (InterruptedException e) {
context.println("interrupted");
} finally {
p.lock.unlock();
}
}
private class PeeringResultPrinter implements RpcPeeringCallback {
private ReentrantLock lock;
private Condition cond;
public PeeringResultPrinter() {
lock = new ReentrantLock();
cond = lock.newCondition();
}
@Override
public void onCompleted(RpcConnection conn) {
try {
lock.lock();
if (conn.getTrustedLevel() == null || conn.getTrustedLevel() == RpcTrustLevel.Untrusted)
context.println(conn.getRemoteAddress() + " peering failed");
else
context.println(conn.getRemoteAddress() + " peering succeeded");
cond.signalAll();
} finally {
lock.unlock();
}
}
}
@ScriptUsage(description = "connect to rpc peer", arguments = {
@ScriptArgument(name = "host", type = "string", description = "host name or ip address"),
@ScriptArgument(name = "port", type = "int", description = "port number", optional = true),
@ScriptArgument(name = "properties", description = "key=value pairs", optional = true) })
public void connect(String[] args) {
doConnect(false, args);
}
@ScriptUsage(description = "ssl connect to rpc peer", arguments = {
@ScriptArgument(name = "host", type = "string", description = "host name or ip address"),
@ScriptArgument(name = "port", type = "int", description = "port number"),
@ScriptArgument(name = "key alias", type = "string", description = "private key (PKCS#12)"),
@ScriptArgument(name = "trust alias", type = "string", description = "CA key (JKS)") })
public void connectSsl(String[] args) {
doConnect(true, args);
}
private void doConnect(boolean isSsl, String[] args) {
try {
String host = InetAddress.getByName(args[0]).getHostAddress();
int port = isSsl ? 7140 : 7139;
String keyAlias = null;
String trustAlias = null;
if (args.length > 1)
port = Integer.parseInt(args[1]);
if (isSsl) {
keyAlias = args[2];
trustAlias = args[3];
}
RpcConnection connection = null;
if (isSsl) {
TrustManagerFactory tmf = keyStoreManager.getTrustManagerFactory(trustAlias, "SunX509");
KeyManagerFactory kmf = keyStoreManager.getKeyManagerFactory(keyAlias, "SunX509");
connection = agent.connectSsl(new RpcConnectionProperties(host, port, kmf, tmf));
} else
connection = agent.connect(new RpcConnectionProperties(host, port));
if (connection != null)
context.printf("%s connected\n", connection.getRemoteAddress());
else
context.println("connect failed");
} catch (UnknownHostException e) {
context.println("unknown host: " + args[0]);
logger.warn("connect failed", e);
} catch (Exception e) {
if (e.getMessage() != null)
context.println(e.getMessage());
else
context.println("connect failed");
logger.warn("connect failed", e);
}
}
@ScriptUsage(description = "terminate connection gracefully", arguments = { @ScriptArgument(name = "cid", type = "int", description = "connection id") })
public void disconnect(String[] args) {
int id = Integer.parseInt(args[0]);
RpcConnection connection = agent.findConnection(id);
if (connection == null) {
context.println("connection not found");
return;
}
connection.close();
context.println("disconnected " + connection.toString());
}
@ScriptUsage(description = "terminate all connections gracefully")
public void disconnectAll(String[] args) {
// guard for concurrent modification exception
Collection<RpcConnection> connections = new ArrayList<RpcConnection>(agent.getConnections());
for (RpcConnection connection : connections) {
if (connection.isOpen())
connection.close();
}
}
// session list of the connection
// at least, there should be one session, "rpc-control"
@ScriptUsage(description = "list all sessions of the connection", arguments = { @ScriptArgument(name = "cid", type = "int", description = "connection id") })
public void sessions(String[] args) {
int id = -1;
try {
id = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
context.println("connection id should be number");
return;
}
RpcConnection conn = agent.findConnection(id);
if (conn == null) {
context.println("connection not found");
return;
}
context.println("RPC Sessions");
context.println("---------------");
for (RpcSession session : conn.getSessions()) {
context.println(session.toString());
}
}
// listening providers
@ScriptUsage(description = "list all bound services", arguments = { @ScriptArgument(name = "cid", type = "int", description = "connection id") })
public void services(String[] args) {
int id = Integer.parseInt(args[0]);
RpcConnection conn = agent.findConnection(id);
if (conn == null) {
context.println("connection not found");
return;
}
context.println("Service Bindings");
context.println("-------------------");
for (RpcServiceBinding binding : conn.getServiceBindings()) {
context.println(binding.toString());
}
}
@ScriptUsage(description = "cancel rpc calls of the connection", arguments = {
@ScriptArgument(name = "cid", type = "int", description = "connection id"),
@ScriptArgument(name = "call id", type = "int", description = "call id") })
public void cancel(String[] args) {
int connId = Integer.parseInt(args[0]);
int callId = Integer.parseInt(args[1]);
RpcConnection conn = agent.findConnection(connId);
RpcBlockingTable blockingTable = conn.getBlockingTable();
blockingTable.cancel(callId);
context.println("call cancelled");
}
@ScriptUsage(description = "list all waiting calls of the connection", arguments = { @ScriptArgument(name = "cid", type = "int", description = "connection id") })
public void waitings(String[] args) {
int id = Integer.parseInt(args[0]);
RpcConnection conn = agent.findConnection(id);
RpcBlockingTable blockingTable = conn.getBlockingTable();
context.println("RPC Waiting Calls");
context.println("---------------------");
for (RpcWaitingCall waiting : blockingTable.getWaitingCalls()) {
context.println(waiting.toString());
}
}
@ScriptUsage(description = "set connection property", arguments = {
@ScriptArgument(name = "cid", type = "int", description = "connection id"),
@ScriptArgument(name = "property key", type = "string", description = "property key name"),
@ScriptArgument(name = "property value", type = "string", description = "property value") })
public void setprop(String[] args) {
try {
int cid = Integer.valueOf(args[0]);
RpcConnection conn = agent.findConnection(cid);
if (conn == null) {
context.println("connection not found");
return;
}
conn.setProperty(args[1], args[2]);
context.println("set");
} catch (NumberFormatException e) {
context.println("invalid connection id format");
}
}
@ScriptUsage(description = "print all connection properties", arguments = { @ScriptArgument(name = "cid", type = "int", description = "connection id") })
public void props(String[] args) {
try {
int cid = Integer.valueOf(args[0]);
RpcConnection conn = agent.findConnection(cid);
if (conn == null) {
context.println("connection not found");
return;
}
context.println("RPC Connection Properties");
context.println("---------------------------");
for (String key : conn.getPropertyKeys())
context.println(key + ": " + conn.getProperty(key));
} catch (NumberFormatException e) {
context.println("invalid connection id format");
}
}
}