/**
* 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.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.logging.Logger;
import kinetic.simulator.SimulatorConfiguration;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import com.seagate.kinetic.common.lib.Hmac;
import com.seagate.kinetic.common.lib.KineticMessage;
import com.seagate.kinetic.proto.Kinetic.Command;
import com.seagate.kinetic.proto.Kinetic.Command.MessageType;
import com.seagate.kinetic.proto.Kinetic.Command.Range;
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.simulator.internal.Authorizer;
import com.seagate.kinetic.simulator.internal.InvalidRequestException;
import com.seagate.kinetic.simulator.internal.KVSecurityException;
import com.seagate.kinetic.simulator.lib.MyLogger;
class RangeException extends Exception {
private static final long serialVersionUID = -6541517825715118652L;
Status.StatusCode status;
RangeException(Status.StatusCode status, String s) {
super(s);
this.status = status;
}
}
public class RangeOp {
private final static Logger LOG = MyLogger.get();
//max key range size.
private static int maxRangeSize = SimulatorConfiguration.getMaxSupportedKeyRangeSize();
static void oops(String s) throws RangeException {
oops(Status.StatusCode.INTERNAL_ERROR, s);
}
static void oops(Status.StatusCode status, String s) throws RangeException {
throw new RangeException(status, s);
}
static void oops(Status.StatusCode status) throws RangeException {
throw new RangeException(status, "");
}
@SuppressWarnings("unchecked")
public static void operation(Store<ByteString, ByteString, KVValue> store,
KineticMessage request, KineticMessage respond, Map<Long, Command.Security.ACL> aclMap) {
ByteString k1 = null, k2 = null;
boolean i1, i2, reverse;
int n;
Command.Builder commandBuilder = (Command.Builder) respond.getCommand();
try {
try {
Range r = request.getCommand().getBody().getRange();
k1 = r.getStartKey();
LOG.fine("k1: " + Hmac.toString(k1));
k2 = r.getEndKey();
LOG.fine("k2: " + Hmac.toString(k2));
i1 = r.getStartKeyInclusive();
i2 = r.getEndKeyInclusive();
n = r.getMaxReturned();
//check max key range size
checkMaxKeyRange (n);
reverse = r.getReverse();
if (n < 1) {
oops("the number of entries is <= 0");
}
List<KVKey> kvKeys = null;
switch (request.getCommand().getHeader().getMessageType()) {
case GETKEYRANGE:
// check permission
Authorizer.checkPermission(aclMap, request.getMessage()
.getHmacAuth().getIdentity(), Permission.RANGE, k1);
Authorizer.checkPermission(aclMap, request.getMessage()
.getHmacAuth().getIdentity(), Permission.RANGE, k2);
if (reverse) {
List<KVKey> l = (ArrayList<KVKey>) store.getRangeReversed(
k1, i1, k2, i2, n);
LOG.fine("getKeyRangeReversed returned " + l.size() + " entries");
kvKeys = filterRawKeysToAuthorizedKeys(l, request.getMessage().getHmacAuth().getIdentity(), aclMap);
} else {
SortedMap<KVKey, KVValue> m = (SortedMap<KVKey, KVValue>) store
.getRange(k1, i1, k2, i2, n);
LOG.fine("getKeyRange returned " + m.size() + " entries");
kvKeys = filterRawKeysToAuthorizedKeys(m.keySet(),
request.getMessage().getHmacAuth().getIdentity(),
aclMap);
}
break;
default:
oops("Unknown request");
}
for (KVKey kvKey : kvKeys) {
LOG.fine("key="
+ Hmac.toString(kvKey.toByteString()));
commandBuilder.getBodyBuilder()
.getRangeBuilder()
.addKeys(kvKey.toByteString());
}
// set ack sequence
commandBuilder
.getHeaderBuilder()
.setAckSequence(
request.getCommand().getHeader().getSequence());
// set status
commandBuilder.getStatusBuilder()
.setCode(Status.StatusCode.SUCCESS);
// TODO check multi-tenant key prefix
} catch (InvalidRequestException ire) {
oops(Status.StatusCode.INVALID_REQUEST,
ire.getMessage());
} catch (KVSecurityException se) {
oops(StatusCode.NOT_AUTHORIZED, se.getMessage());
} catch (Exception e) {
LOG.fine(e.toString());
Writer writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
LOG.fine(writer.toString());
oops(Status.StatusCode.INTERNAL_ERROR,
"Opps2: " + e.getMessage() + "--" + e.toString());
}
} catch (RangeException e) {
commandBuilder.getStatusBuilder().setCode(e.status);
commandBuilder.getStatusBuilder()
.setStatusMessage(e.getMessage());
} finally {
switch (request.getCommand().getHeader().getMessageType()) {
case GETKEYRANGE:
commandBuilder.getHeaderBuilder()
.setMessageType(MessageType.GETKEYRANGE_RESPONSE);
break;
default:
break;
}
}
}
/**
* Iterates over given keys and builds a list of the first contiguous block of keys for which the given use
* has the RANGE role. Upon finding the first key in the given range that is not allowed, short circuits and
* returns the current (possibly empty) list.
*
* @param rawKeys The keys returned by the store
* @param user The user requesting the keys, required to have RANGE role for all keys returned
* @param aclMap The ACL Map
* @return
* @throws KVSecurityException
*/
public static List<KVKey> filterRawKeysToAuthorizedKeys(Iterable<KVKey> rawKeys, long user, Map<Long, Command.Security.ACL> aclMap) throws KVSecurityException {
List<KVKey> rangeAllowedKeys = Lists.newArrayList();
for (KVKey key : rawKeys) {
LOG.fine("Checking RANGE permission on key <" + key + "> for user <" + user + "> ");
if (Authorizer.hasPermission(aclMap, user,
Command.Security.ACL.Permission.RANGE, key.toByteString())) {
LOG.fine("Permission found");
rangeAllowedKeys.add(key);
} else {
// Short-circuit here, at the first disallowed key.
LOG.fine("No permission found, stopping RANGE permission check here.");
return rangeAllowedKeys;
}
}
return rangeAllowedKeys;
}
private static void checkMaxKeyRange (int len) throws InvalidRequestException {
if (len > maxRangeSize) {
throw new InvalidRequestException ("request key range exceeds allowed size " + maxRangeSize + ", request size = " + len);
}
}
}