/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.agent.transport;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.util.List;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.exception.UnsupportedVersionException;
import com.cloud.storage.VolumeVO;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.exception.CloudRuntimeException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
/**
* Request is a simple wrapper around command and answer to add sequencing,
* versioning, and flags. Note that the version here represents the changes
* in the over the wire protocol. For example, if we decide to not use Gson.
* It does not version the changes in the actual commands. That's expected
* to be done by adding new classes to the command and answer list.
*
* A request looks as follows:
* 1. Version - 1 byte;
* 2. Flags - 3 bytes;
* 3. Sequence - 8 bytes;
* 4. Length - 4 bytes;
* 5. ManagementServerId - 8 bytes;
* 6. AgentId - 8 bytes;
* 7. Data Package.
*
* Currently flags has only if it is a request or response.
*/
public class Request {
private static final Logger s_logger = Logger.getLogger(Request.class);
public enum Version {
v1, // using gson to marshall
v2, // now using gson as marshalled.
v3; // Adding routing information into the Request data structure.
public static Version get(final byte ver) throws UnsupportedVersionException {
for (final Version version : Version.values()) {
if (ver == version.ordinal())
return version;
}
throw new UnsupportedVersionException("Can't lookup version: " + ver, UnsupportedVersionException.UnknownVersion);
}
};
protected static final short FLAG_RESPONSE = 0x0;
protected static final short FLAG_REQUEST = 0x1;
protected static final short FLAG_STOP_ON_ERROR = 0x2;
protected static final short FLAG_IN_SEQUENCE = 0x4;
protected static final short FLAG_WATCH = 0x8;
protected static final short FLAG_UPDATE = 0x10;
protected static final short FLAG_FROM_SERVER = 0x20;
protected static final short FLAG_CONTROL = 0x40;
protected static final GsonBuilder s_gBuilder;
static {
s_gBuilder = new GsonBuilder();
s_gBuilder.registerTypeAdapter(Command[].class, new ArrayTypeAdaptor<Command>());
s_gBuilder.registerTypeAdapter(Answer[].class, new ArrayTypeAdaptor<Answer>());
final Type listType = new TypeToken<List<VolumeVO>>() {}.getType();
s_gBuilder.registerTypeAdapter(listType, new VolListTypeAdaptor());
s_logger.info("Builder inited.");
}
public static GsonBuilder initBuilder() {
return s_gBuilder;
}
protected Version _ver;
protected long _seq;
protected Command[] _cmds;
protected boolean _inSequence;
protected boolean _stopOnError;
protected boolean _fromServer;
protected boolean _control;
protected long _mgmtId;
protected long _agentId;
protected String _content;
public Request(long seq, long agentId, long mgmtId, final Command command, boolean fromServer) {
this(seq, agentId, mgmtId, new Command[] {command}, true, fromServer);
}
public Request(long seq, long agentId, long mgmtId, final Command[] commands, boolean fromServer) {
this(seq, agentId, mgmtId, commands, true, fromServer);
}
protected Request(Version ver, long seq, long agentId, long mgmtId, final Command[] cmds, final Boolean inSequence, final boolean stopOnError, boolean fromServer) {
_ver = ver;
_cmds = cmds;
_stopOnError = stopOnError;
if (inSequence != null) {
_inSequence = inSequence;
} else {
for (final Command cmd : cmds) {
if (cmd.executeInSequence()) {
_inSequence = true;
break;
}
}
}
_seq = seq;
_agentId = agentId;
_mgmtId = mgmtId;
_fromServer = fromServer;
}
protected Request(Version ver, long seq, long agentId, long mgmtId, final String content, final boolean inSequence, final boolean stopOnError, final boolean fromServer, final boolean control) {
_ver = ver;
_cmds = null;
_content = content;
_stopOnError = stopOnError;
_inSequence = inSequence;
_seq = seq;
_agentId = agentId;
_mgmtId = mgmtId;
_fromServer = fromServer;
_control = control;
}
public Request(long seq, long agentId, long mgmtId, final Command[] cmds, final boolean stopOnError, boolean fromServer) {
this(Version.v3, seq, agentId, mgmtId, cmds, null, stopOnError, fromServer);
}
public boolean isControl() {
return _control;
}
public void setControl() {
_control = true;
}
public long getManagementServerId() {
return _mgmtId;
}
protected Request(final Request that, final Command[] cmds) {
this._ver = that._ver;
this._seq = that._seq;
this._inSequence = that._inSequence;
this._stopOnError = that._stopOnError;
this._cmds = cmds;
this._mgmtId = that._mgmtId;
this._agentId = that._agentId;
this._fromServer = !that._fromServer;
}
protected Request() {
}
public Version getVersion() {
return _ver;
}
public void setAgentId(long agentId) {
_agentId = agentId;
}
public boolean executeInSequence() {
return _inSequence;
}
public long getSequence() {
return _seq;
}
public boolean stopOnError() {
return _stopOnError;
}
public Command getCommand() {
getCommands();
return _cmds[0];
}
public Command[] getCommands() {
if (_cmds == null) {
final Gson json = s_gBuilder.create();
_cmds = json.fromJson(_content, Command[].class);
}
return _cmds;
}
/**
* Use this only surrounded by debug.
*/
@Override
public String toString() {
String content = _content;
if (content == null) {
final Gson gson = s_gBuilder.create();
content = gson.toJson(_cmds);
}
final StringBuilder buffer = new StringBuilder();
buffer.append("{ ").append(getType());
buffer.append(", Seq: ").append(_seq).append(", Ver: ").append(_ver.toString()).append(", MgmtId: ").append(_mgmtId).append(", AgentId: ").append(_agentId).append(", Flags: ").append(Integer.toBinaryString(getFlags()));
buffer.append(", ").append(content).append(" }");
return buffer.toString();
}
protected String getType() {
return "Cmd ";
}
protected ByteBuffer serializeHeader(final int contentSize) {
final ByteBuffer buffer = ByteBuffer.allocate(32);
buffer.put(getVersionInByte());
buffer.put((byte)0);
buffer.putShort(getFlags());
buffer.putLong(_seq);
buffer.putInt(contentSize);
buffer.putLong(_mgmtId);
buffer.putLong(_agentId);
buffer.flip();
return buffer;
}
public ByteBuffer[] toBytes() {
final Gson gson = s_gBuilder.create();
final ByteBuffer[] buffers = new ByteBuffer[2];
if (_content == null) {
_content = gson.toJson(_cmds, _cmds.getClass());
}
buffers[1] = ByteBuffer.wrap(_content.getBytes());
buffers[0] = serializeHeader(buffers[1].capacity());
return buffers;
}
public byte[] getBytes() {
final ByteBuffer[] buffers = toBytes();
final int len1 = buffers[0].remaining();
final int len2 = buffers[1].remaining();
final byte[] bytes = new byte[len1 + len2];
buffers[0].get(bytes, 0, len1);
buffers[1].get(bytes, len1, len2);
return bytes;
}
protected byte getVersionInByte() {
return (byte)_ver.ordinal();
}
protected short getFlags() {
short flags = 0;
if (!(this instanceof Response)) {
flags = FLAG_REQUEST;
} else {
flags = FLAG_RESPONSE;
}
if (_inSequence) {
flags = (short)(flags | FLAG_IN_SEQUENCE);
}
if (_stopOnError) {
flags = (short)(flags | FLAG_STOP_ON_ERROR);
}
if (_fromServer) {
flags = (short)(flags | FLAG_FROM_SERVER);
}
if (_control) {
flags = (short)(flags | FLAG_CONTROL);
}
return flags;
}
/**
* Factory method for Request and Response. It expects the bytes to be
* correctly formed so it's possible that it throws underflow exceptions
* but you shouldn't be concerned about that since that all bytes sent in
* should already be formatted correctly.
*
* @param bytes bytes to be converted.
* @return Request or Response depending on the data.
* @throws ClassNotFoundException if the Command or Answer can not be formed.
* @throws
*/
public static Request parse(final byte[] bytes) throws ClassNotFoundException, UnsupportedVersionException {
final ByteBuffer buff = ByteBuffer.wrap(bytes);
final byte ver = buff.get();
final Version version = Version.get(ver);
if (version.ordinal() < Version.v3.ordinal()) {
throw new UnsupportedVersionException("This version is no longer supported: " + version.toString(), UnsupportedVersionException.IncompatibleVersion);
}
final byte reserved = buff.get(); // tossed away for now.
final Short flags = buff.getShort();
final boolean isRequest = (flags & FLAG_REQUEST) > 0;
final boolean isControl = (flags & FLAG_IN_SEQUENCE) > 0;
final boolean isStopOnError = (flags & FLAG_STOP_ON_ERROR) > 0;
final boolean isWatch = (flags & FLAG_WATCH) > 0;
final boolean fromServer = (flags & FLAG_FROM_SERVER) > 0;
final boolean needsUpdate = (flags & FLAG_UPDATE) > 0;
final boolean control = (flags & FLAG_CONTROL) > 0;
final long seq = buff.getLong();
final int size = buff.getInt();
final long mgmtId = buff.getLong();
final long agentId = buff.getLong();
byte[] command = null;
int offset = 0;
if (buff.hasArray()) {
command = buff.array();
offset = buff.arrayOffset() + buff.position();
} else {
command = new byte[buff.remaining()];
buff.get(command);
offset = 0;
}
final String content = new String(command, offset, command.length - offset);
if (needsUpdate && !isRequest) {
return new UpgradeResponse(Version.get(ver), seq, content);
}
if (isRequest) {
return new Request(version, seq, agentId, mgmtId, content, isControl, isStopOnError, fromServer, control);
} else {
return new Response(Version.get(ver), seq, agentId, mgmtId, content, isControl, isStopOnError, fromServer, control);
}
}
public long getAgentId() {
return _agentId;
}
public static boolean requiresSequentialExecution(final byte[] bytes) {
return (bytes[3] & FLAG_IN_SEQUENCE) > 0;
}
public static Version getVersion(final byte[] bytes) throws UnsupportedVersionException {
try {
return Version.get(bytes[0]);
} catch (UnsupportedVersionException e) {
throw new CloudRuntimeException("Unsupported version: " + bytes[0]);
}
}
public static long getManagementServerId(final byte[] bytes) {
return NumbersUtil.bytesToLong(bytes, 16);
}
public static long getAgentId(final byte[] bytes) {
return NumbersUtil.bytesToLong(bytes, 24);
}
public static boolean fromServer(final byte[] bytes) {
// int flags = NumbersUtil.bytesToShort(bytes, 2);
return (bytes[3] & FLAG_FROM_SERVER) > 0;
}
public static boolean isRequest(final byte[] bytes) {
return (bytes[3] & FLAG_REQUEST) > 0;
}
public static long getSequence(final byte[] bytes) {
return NumbersUtil.bytesToLong(bytes, 4);
}
public static boolean isControl(final byte[] bytes) {
// int flags = NumbersUtil.bytesToShort(bytes, 2);
return (bytes[3] & FLAG_CONTROL) > 0;
}
}