/**
* Copyright 2013-2015 Seagate Technology LLC.
*
* This Source Code Form is subject to the terms of the Mozilla
* Public License, v. 2.0. If a copy of the MPL was not
* distributed with this file, You can obtain one at
* https://mozilla.org/MP:/2.0/.
*
* This program is distributed in the hope that it will be useful,
* but is provided AS-IS, WITHOUT ANY WARRANTY; including without
* the implied warranty of MERCHANTABILITY, NON-INFRINGEMENT or
* FITNESS FOR A PARTICULAR PURPOSE. See the Mozilla Public
* License for more details.
*
* See www.openkinetic.org for more project information
*/
package com.seagate.kinetic.simulator.persist;
import java.util.Map;
import java.util.logging.Logger;
import kinetic.simulator.SimulatorConfiguration;
import com.google.protobuf.ByteString;
import com.seagate.kinetic.common.lib.KineticMessage;
import com.seagate.kinetic.proto.Kinetic.Command;
import com.seagate.kinetic.proto.Kinetic.Command.Algorithm;
import com.seagate.kinetic.proto.Kinetic.Command.KeyValue;
import com.seagate.kinetic.proto.Kinetic.Command.MessageType;
import com.seagate.kinetic.proto.Kinetic.Command.Security.ACL;
import com.seagate.kinetic.proto.Kinetic.Command.Security.ACL.Permission;
import com.seagate.kinetic.proto.Kinetic.Command.Status;
import com.seagate.kinetic.proto.Kinetic.Command.Status.StatusCode;
import com.seagate.kinetic.proto.Kinetic.Command.Synchronization;
import com.seagate.kinetic.simulator.internal.Authorizer;
import com.seagate.kinetic.simulator.internal.InvalidRequestException;
import com.seagate.kinetic.simulator.internal.KVSecurityException;
import com.seagate.kinetic.simulator.internal.KVStoreException;
import com.seagate.kinetic.simulator.internal.KVStoreNotFound;
import com.seagate.kinetic.simulator.internal.KVStoreVersionMismatch;
import com.seagate.kinetic.simulator.lib.MyLogger;
class KvException extends Exception {
private static final long serialVersionUID = -6541517825715118652L;
Status.StatusCode status;
KvException(Status.StatusCode status, String s) {
super(s);
this.status = status;
}
}
public class KVOp {
private final static Logger LOG = MyLogger.get();
//max value size
private static long maxValueSize = SimulatorConfiguration
.getMaxSupportedValueSize();
//max key size
private static int maxKeySize = SimulatorConfiguration.getMaxSupportedKeySize();
//max version size
private static int maxVersionSize = SimulatorConfiguration.getMaxSupportedVersionSize();
static void handleException(String s) throws KvException {
handleException(Status.StatusCode.INTERNAL_ERROR, s);
}
static void handleException(Status.StatusCode status, String s) throws KvException {
//LOG.warning("throwing exception., status code = " + status + ", msg = " +s);
throw new KvException(status, s);
}
static void handleException(Status.StatusCode status) throws KvException {
throw new KvException(status, "");
}
public static void processRequest(Map<Long, ACL> aclmap,
Store<ByteString, ByteString, KVValue> store, KineticMessage kmreq,
KineticMessage kmresp) {
//Message request = (Message) kmreq.getMessage();
Command.Builder commandBuilder = (Command.Builder) kmresp.getCommand();
PersistOption persistOption = PersistOption.SYNC;
try {
// KV in;
KeyValue requestKeyValue = kmreq.getCommand().getBody()
.getKeyValue();
// kv out
KeyValue.Builder respondKeyValue = commandBuilder
.getBodyBuilder().getKeyValueBuilder();
boolean metadataOnly = requestKeyValue.getMetadataOnly();
try {
// set ack sequence
commandBuilder
.getHeaderBuilder()
.setAckSequence(
kmreq.getCommand().getHeader().getSequence());
// key = in.getKey();
ByteString key = requestKeyValue.getKey();
KVValue storeEntry = null;
// perform key value op
switch (kmreq.getCommand().getHeader().getMessageType()) {
case GET:
// get entry from store
try {
//check max key length
checkMaxKeyLenth (key.size());
Authorizer.checkPermission(aclmap, kmreq.getMessage()
.getHmacAuth().getIdentity(),Permission.READ, key);
storeEntry = store.get(key);
// respond metadata
respondKeyValue.setKey(storeEntry.getKeyOf());
respondKeyValue.setDbVersion(storeEntry.getVersion());
respondKeyValue.setTag(storeEntry.getTag());
// set algorithm only if it was set by application
if (storeEntry.hasAlgorithm()) {
respondKeyValue.setAlgorithm(storeEntry.getAlgorithm());
}
// respond value
if (!metadataOnly) {
// respond.setValue(storeEntry.getData());
//byte[] bytes = storeEntry.getData().toByteArray();
kmresp.setValue(storeEntry.getData().toByteArray());
}
} finally {
// respond message type
commandBuilder.getHeaderBuilder()
.setMessageType(MessageType.GET_RESPONSE);
}
break;
case PUT:
try {
// persist option
persistOption = getPersistOption(requestKeyValue);
checkWrite(aclmap, kmreq, Permission.WRITE);
ByteString valueByteString = null;
if (kmreq.getValue() != null) {
valueByteString = ByteString.copyFrom(kmreq
.getValue());
} else {
// set value to empty if null
valueByteString = ByteString.EMPTY;
}
Algorithm al = null;
if (requestKeyValue.hasAlgorithm()) {
al = requestKeyValue.getAlgorithm();
}
KVValue data = new KVValue(requestKeyValue.getKey(),
requestKeyValue.getNewVersion(),
requestKeyValue.getTag(),
al, valueByteString);
if (requestKeyValue.getForce()) {
store.putForced(key, data, persistOption);
} else {
// put to store
// data.setVersion(requestKeyValue.getNewVersion());
ByteString oldVersion = requestKeyValue
.getDbVersion();
store.put(key, oldVersion, data, persistOption);
}
} finally {
// respond message type
commandBuilder.getHeaderBuilder()
.setMessageType(MessageType.PUT_RESPONSE);
}
break;
case DELETE:
try {
// persist option
persistOption = getPersistOption(requestKeyValue);
checkWrite(aclmap, kmreq, Permission.DELETE);
if (requestKeyValue.getForce()) {
store.deleteForced(key, persistOption);
} else {
store.delete(requestKeyValue.getKey(),
requestKeyValue.getDbVersion(), persistOption);
}
} finally {
// respond message type
commandBuilder.getHeaderBuilder()
.setMessageType(MessageType.DELETE_RESPONSE);
}
break;
case GETVERSION:
try {
//check max key length
checkMaxKeyLenth (key.size());
Authorizer.checkPermission(aclmap, kmreq.getMessage()
.getHmacAuth().getIdentity(), Permission.READ,
key);
storeEntry = store.get(key);
respondKeyValue.setDbVersion(storeEntry.getVersion());
} finally {
// respond message type
commandBuilder
.getHeaderBuilder()
.setMessageType(MessageType.GETVERSION_RESPONSE);
}
break;
case GETNEXT:
try {
//check max key length
checkMaxKeyLenth (key.size());
storeEntry = store.getNext(key);
ByteString nextKey = storeEntry.getKeyOf();
// We must verify that the next key is readable, not the passed key
Authorizer.checkPermission(aclmap, kmreq.getMessage()
.getHmacAuth().getIdentity(), Permission.READ,
nextKey);
respondKeyValue.setKey(nextKey);
respondKeyValue.setTag(storeEntry.getTag());
respondKeyValue.setDbVersion(storeEntry.getVersion());
if (storeEntry.hasAlgorithm()) {
respondKeyValue.setAlgorithm(storeEntry.getAlgorithm());
}
if (!metadataOnly) {
// respond.setValue(storeEntry.getData());
kmresp.setValue(storeEntry.getData().toByteArray());
}
} finally {
// respond message type
commandBuilder.getHeaderBuilder()
.setMessageType(MessageType.GETNEXT_RESPONSE);
}
break;
case GETPREVIOUS:
try {
//check max key length
checkMaxKeyLenth (key.size());
storeEntry = store.getPrevious(key);
ByteString previousKey = storeEntry.getKeyOf();
// We must verify that the previous key is readable, not the passed key
Authorizer.checkPermission(aclmap, kmreq.getMessage()
.getHmacAuth().getIdentity(), Permission.READ,
previousKey);
respondKeyValue.setKey(previousKey);
respondKeyValue.setTag(storeEntry.getTag());
respondKeyValue.setDbVersion(storeEntry.getVersion());
if (storeEntry.hasAlgorithm()) {
respondKeyValue.setAlgorithm(storeEntry.getAlgorithm());
}
if (!metadataOnly) {
// respond.setValue(storeEntry.getData());
kmresp.setValue(storeEntry.getData().toByteArray());
}
} finally {
// respond message type
commandBuilder
.getHeaderBuilder()
.setMessageType(
MessageType.GETPREVIOUS_RESPONSE);
}
break;
default:
handleException("Unknown request");
}
} catch (KVStoreNotFound e) {
handleException(Status.StatusCode.NOT_FOUND);
} catch (KVStoreVersionMismatch e) {
handleException(Status.StatusCode.VERSION_MISMATCH);
} catch (KVStoreException e) {
handleException(Status.StatusCode.INTERNAL_ERROR,
"Opps1: " + e.getMessage());
} catch (KVSecurityException e) {
handleException(StatusCode.NOT_AUTHORIZED, e.getMessage());
} catch (InvalidRequestException e) {
handleException(StatusCode.INVALID_REQUEST, e.getMessage());
} catch (Exception e) {
handleException(Status.StatusCode.INTERNAL_ERROR, e.getMessage());
}
// respond status
commandBuilder.getStatusBuilder()
.setCode(Status.StatusCode.SUCCESS);
} catch (KvException e) {
LOG.warning ("KV op Exception: " + e.status + ": " + e.getMessage());
commandBuilder.getStatusBuilder().setCode(e.status);
commandBuilder.getStatusBuilder()
.setStatusMessage(e.getMessage());
}
}
/**
* Check if request value size is within the max allowed size.
*
* @param request
* request message
*
* @return true if less than max allowed size. Otherwise, returned false.
*/
private static boolean isSupportedValueSize(KineticMessage km) {
boolean supported = false;
byte[] value = km.getValue();
if (value == null || value.length <= maxValueSize) {
// value not set, this may happen if client library did not set
// value as EMPTY for null value.
supported = true;
}
return supported;
}
/**
*
* Get db persist option. Default is set to SYNC if not set.
*
* @param kv
* KeyValue element.
*
* @return persist option.
* @throws KvException if invalid synchronization option is set in the kv parameter.
*/
public static PersistOption getPersistOption(KeyValue kv)
throws InvalidRequestException {
/**
* synchronization must be set to a valid value.
*/
if (kv.hasSynchronization() == false) {
throw new InvalidRequestException(
"Persistent synchronization option must be set to a valid value");
}
PersistOption option = PersistOption.SYNC;
/**
* if set, must be an valid option.
*/
Synchronization sync = kv.getSynchronization();
switch (sync) {
case WRITETHROUGH:
case FLUSH:
option = PersistOption.SYNC;
break;
case WRITEBACK:
option = PersistOption.ASYNC;
break;
default:
throw new InvalidRequestException(
"Invalid persistent synchronization option: " + sync);
}
return option;
}
private static void checkMaxKeyLenth(int len)
throws InvalidRequestException {
if (len > maxKeySize) {
throw new InvalidRequestException ("key length exceeds max size, size=" + maxKeySize + ", request size=" + len);
}
}
private static void checkMaxVersionLength(ByteString bs)
throws InvalidRequestException {
int len = 0;
if (bs != null) {
len = bs.size();
}
if ( len > maxVersionSize) {
throw new InvalidRequestException ("max version exceeds max allowed size " + maxVersionSize + ", request version size=" + len);
}
}
/**
* Check if request has write privilege, key length, version length.
*
* @param aclmap
* acl map for this instance
* @param kmreq
* request message
* @param role
* role to access
* @throws InvalidRequestException
* @throws KVSecurityException
*/
public static void checkWrite(Map<Long, ACL> aclmap, KineticMessage kmreq,
Permission role)
throws InvalidRequestException,
KVSecurityException {
KeyValue requestKeyValue = kmreq.getCommand().getBody().getKeyValue();
ByteString key = requestKeyValue.getKey();
// check max key length
checkMaxKeyLenth(key.size());
// check max version size
ByteString bs = requestKeyValue.getNewVersion();
checkMaxVersionLength(bs);
if (isSupportedValueSize(kmreq) == false) {
throw new InvalidRequestException(
"value size exceeded max supported size. Supported size: "
+ maxValueSize + ", received size="
+ kmreq.getValue().length + " (in bytes)");
}
// check permission
Authorizer.checkPermission(aclmap, kmreq.getMessage().getHmacAuth()
.getIdentity(), role, key);
}
}