// Copyright 2012 Citrix Systems, Inc. Licensed under the // Apache License, Version 2.0 (the "License"); you may not use this // file except in compliance with the License. Citrix Systems, Inc. // reserves all rights not expressly granted by 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. // // Automatically generated by addcopyright.py at 04/03/2012 package com.cloud.agent.transport; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.apache.log4j.Level; import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig; import com.cloud.exception.UnsupportedVersionException; import com.cloud.serializer.GsonHelper; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonParseException; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.stream.JsonReader; /** * 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. * */ public class Request { private static final Logger s_logger = Logger.getLogger(Request.class); protected static final Gson s_gson = GsonHelper.getGson(); protected static final Gson s_gogger = GsonHelper.getGsonLogger(); protected static final Logger s_gsonLogger = GsonHelper.getLogger(); 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_FROM_SERVER = 0x20; protected static final short FLAG_CONTROL = 0x40; protected static final short FLAG_COMPRESSED = 0x80; protected Version _ver; protected long _session; protected long _seq; protected short _flags; protected long _mgmtId; protected long _via; protected long _agentId; protected Command[] _cmds; protected String _content; protected Request() { } protected Request(Version ver, long seq, long agentId, long mgmtId, long via, short flags, final Command[] cmds) { _ver = ver; _cmds = cmds; _flags = flags; _seq = seq; _via = via; _agentId = agentId; _mgmtId = mgmtId; setInSequence(cmds); } protected Request(Version ver, long seq, long agentId, long mgmtId, short flags, final Command[] cmds) { this(ver, seq, agentId, mgmtId, agentId, flags, cmds); } protected Request(Version ver, long seq, long agentId, long mgmtId, long via, short flags, final String content) { this(ver, seq, agentId, mgmtId, via, flags, (Command[])null); _content = content; } public Request(long agentId, long mgmtId, Command command, boolean fromServer) { this(agentId, mgmtId, new Command[] { command }, true, fromServer); } public Request(long agentId, long mgmtId, Command[] cmds, boolean stopOnError, boolean fromServer) { this(Version.v1, -1l, agentId, mgmtId, (short)0, cmds); setStopOnError(stopOnError); setFromServer(fromServer); } public void setSequence(long seq) { _seq = seq; } protected void setInSequence(Command[] cmds) { if (cmds == null) { return; } for (Command cmd : cmds) { if (cmd.executeInSequence()) { setInSequence(true); break; } } } protected Request(final Request that, final Command[] cmds) { this._ver = that._ver; this._seq = that._seq; setInSequence(that.executeInSequence()); setStopOnError(that.stopOnError()); this._cmds = cmds; this._mgmtId = that._mgmtId; this._via = that._via; this._agentId = that._agentId; setFromServer(!that.isFromServer()); } private final void setStopOnError(boolean stopOnError) { _flags |= (stopOnError ? FLAG_STOP_ON_ERROR : 0); } private final void setInSequence(boolean inSequence) { _flags |= (inSequence ? FLAG_IN_SEQUENCE : 0); } public boolean isControl() { return (_flags & FLAG_CONTROL) > 0; } public void setControl(boolean control) { _flags |= (control ? FLAG_CONTROL : 0); } private final void setFromServer(boolean fromServer) { _flags |= (fromServer ? FLAG_FROM_SERVER : 0); } public long getManagementServerId() { return _mgmtId; } public boolean isFromServer() { return (_flags & FLAG_FROM_SERVER) > 0; } public Version getVersion() { return _ver; } public void setAgentId(long agentId) { _agentId = agentId; } public void setVia(long viaId) { _via = viaId; } public boolean executeInSequence() { return (_flags & FLAG_IN_SEQUENCE) > 0; } public long getSequence() { return _seq; } public boolean stopOnError() { return (_flags & FLAG_STOP_ON_ERROR) > 0; } public Command getCommand() { getCommands(); return _cmds[0]; } public Command[] getCommands() { if (_cmds == null) { try { StringReader reader = new StringReader(_content); JsonReader jsonReader = new JsonReader(reader); jsonReader.setLenient(true); _cmds = s_gson.fromJson(jsonReader, (Type)Command[].class); } catch (RuntimeException e) { s_logger.error("Caught problem with " + _content, e); throw e; } } return _cmds; } protected String getType() { return "Cmd "; } protected ByteBuffer serializeHeader(final int contentSize) { final ByteBuffer buffer = ByteBuffer.allocate(40); buffer.put(getVersionInByte()); buffer.put((byte) 0); buffer.putShort(getFlags()); buffer.putLong(_seq); // The size here is uncompressed size, if the data is compressed. buffer.putInt(contentSize); buffer.putLong(_mgmtId); buffer.putLong(_agentId); buffer.putLong(_via); buffer.flip(); return buffer; } public static ByteBuffer doDecompress(ByteBuffer buffer, int length) { byte[] byteArrayIn = new byte[1024]; ByteArrayInputStream byteIn; if (buffer.hasArray()) { byteIn = new ByteArrayInputStream(buffer.array(), buffer.position() + buffer.arrayOffset(), buffer.remaining()); } else { byte[] array = new byte[buffer.limit() - buffer.position()]; buffer.get(array); byteIn = new ByteArrayInputStream(array); } ByteBuffer retBuff = ByteBuffer.allocate(length); int len = 0; try { GZIPInputStream in = new GZIPInputStream(byteIn); while ((len = in.read(byteArrayIn)) > 0) { retBuff.put(byteArrayIn, 0, len); } in.close(); } catch (IOException e) { s_logger.error("Fail to decompress the request!", e); } retBuff.flip(); return retBuff; } public static ByteBuffer doCompress(ByteBuffer buffer, int length) { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(length); byte[] array; if (buffer.hasArray()) { array = buffer.array(); } else { array = new byte[buffer.capacity()]; buffer.get(array); } try { GZIPOutputStream out = new GZIPOutputStream(byteOut, length); out.write(array); out.finish(); out.close(); } catch (IOException e) { s_logger.error("Fail to compress the request!", e); } return ByteBuffer.wrap(byteOut.toByteArray()); } public ByteBuffer[] toBytes() { final ByteBuffer[] buffers = new ByteBuffer[2]; ByteBuffer tmp; if (_content == null) { _content = s_gson.toJson(_cmds, _cmds.getClass()); } tmp = ByteBuffer.wrap(_content.getBytes()); int capacity = tmp.capacity(); /* Check if we need to compress the data */ if (capacity >= 8192) { tmp = doCompress(tmp, capacity); _flags |= FLAG_COMPRESSED; } buffers[1] = tmp; buffers[0] = serializeHeader(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() { return (short) (((this instanceof Response) ? FLAG_RESPONSE : FLAG_REQUEST) | _flags); } public void logD(String msg) { logD(msg, true); } public void logD(String msg, boolean logContent) { if (s_logger.isDebugEnabled()) { String log = log(msg, logContent, Level.DEBUG); if (log != null) { s_logger.debug(log); } } } public void logT(String msg, boolean logD) { if (s_logger.isTraceEnabled()) { String log = log(msg, true, Level.TRACE); if (log != null) { s_logger.trace(log); } } else if (logD && s_logger.isDebugEnabled()) { String log = log(msg, false, Level.DEBUG); if (log != null) { s_logger.debug(log); } } } @Override public String toString() { return log("", true, Level.DEBUG); } protected String log(String msg, boolean logContent, Level level) { StringBuilder content = new StringBuilder(); if (logContent) { if (_cmds == null) { try { _cmds = s_gson.fromJson(_content, this instanceof Response ? Answer[].class : Command[].class); } catch (RuntimeException e) { s_logger.error("Unable to convert to json: " + _content); throw e; } } try { s_gogger.toJson(_cmds, content); } catch (Throwable e) { StringBuilder buff = new StringBuilder(); for (Command cmd : _cmds) { buff.append(cmd.getClass().getSimpleName()).append("/"); } s_logger.error("Gson serialization error " + buff.toString(), e); assert false : "More gson errors on " + buff.toString(); return ""; } if (content.length() <= (1 + _cmds.length * 3)) { return null; } } else { if (_cmds == null) { _cmds = s_gson.fromJson(_content, this instanceof Response ? Answer[].class : Command[].class); } content.append("{ "); for (Command cmd : _cmds) { content.append(cmd.getClass().getSimpleName()).append(", "); } content.replace(content.length() - 2, content.length(), " }"); } StringBuilder buf = new StringBuilder("Seq "); buf.append(_agentId).append("-").append(_seq).append(": "); buf.append(msg); buf.append(" { ").append(getType()); buf.append(", MgmtId: ").append(_mgmtId).append(", via: ").append(_via); buf.append(", Ver: ").append(_ver.toString()); buf.append(", Flags: ").append(Integer.toBinaryString(getFlags())).append(", "); buf.append(content); buf.append(" }"); return buf.toString(); } /** * 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 { ByteBuffer buff = ByteBuffer.wrap(bytes); final byte ver = buff.get(); final Version version = Version.get(ver); if (version.ordinal() != Version.v1.ordinal() && 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 long seq = buff.getLong(); // The size here is uncompressed size, if the data is compressed. final int size = buff.getInt(); final long mgmtId = buff.getLong(); final long agentId = buff.getLong(); long via; if (version.ordinal() == Version.v1.ordinal()) { via = buff.getLong(); } else { via = agentId; } if ((flags & FLAG_COMPRESSED) != 0) { buff = doDecompress(buff, size); } 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 (isRequest) { return new Request(version, seq, agentId, mgmtId, via, flags, content); } else { return new Response(Version.get(ver), seq, agentId, mgmtId, via, flags, content); } } public long getAgentId() { return _agentId; } public long getViaAgentId() { return _via; } 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 long getViaAgentId(final byte[] bytes) { return NumbersUtil.bytesToLong(bytes, 32); } public static boolean fromServer(final byte[] bytes) { 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) { return (bytes[3] & FLAG_CONTROL) > 0; } public static class NwGroupsCommandTypeAdaptor implements JsonDeserializer<Pair<Long, Long>>, JsonSerializer<Pair<Long, Long>> { public NwGroupsCommandTypeAdaptor() { } @Override public JsonElement serialize(Pair<Long, Long> src, java.lang.reflect.Type typeOfSrc, JsonSerializationContext context) { JsonArray array = new JsonArray(); if (src.first() != null) { array.add(s_gson.toJsonTree(src.first())); } else { array.add(new JsonNull()); } if (src.second() != null) { array.add(s_gson.toJsonTree(src.second())); } else { array.add(new JsonNull()); } return array; } @Override public Pair<Long, Long> deserialize(JsonElement json, java.lang.reflect.Type type, JsonDeserializationContext context) throws JsonParseException { Pair<Long, Long> pairs = new Pair<Long, Long>(null, null); JsonArray array = json.getAsJsonArray(); if (array.size() != 2) { return pairs; } JsonElement element = array.get(0); if (!element.isJsonNull()) { pairs.first(element.getAsLong()); } element = array.get(1); if (!element.isJsonNull()) { pairs.second(element.getAsLong()); } return pairs; } } public static class PortConfigListTypeAdaptor implements JsonDeserializer<List<PortConfig>>, JsonSerializer<List<PortConfig>> { public PortConfigListTypeAdaptor() { } @Override public JsonElement serialize(List<PortConfig> src, Type typeOfSrc, JsonSerializationContext context) { if (src.size() == 0) { return new JsonNull(); } JsonArray array = new JsonArray(); for (PortConfig pc : src) { array.add(s_gson.toJsonTree(pc)); } return array; } @Override public List<PortConfig> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonNull()) { return new ArrayList<PortConfig>(); } List<PortConfig> pcs = new ArrayList<PortConfig>(); JsonArray array = json.getAsJsonArray(); Iterator<JsonElement> it = array.iterator(); while (it.hasNext()) { JsonElement element = it.next(); pcs.add(s_gson.fromJson(element, PortConfig.class)); } return pcs; } } }