/java/src/com/google/appengine/tools/mapreduce/impl/ShuffleServiceImpl.java

http://appengine-mapreduce.googlecode.com/ · Java · 117 lines · 91 code · 10 blank · 16 comment · 8 complexity · 9c2c15b5664639dd7b6fa4159b84c56e MD5 · raw file

  1. // Copyright 2012 Google Inc. All Rights Reserved.
  2. package com.google.appengine.tools.mapreduce.impl;
  3. import com.google.appengine.api.files.AppEngineFile;
  4. import com.google.appengine.api.files.FileServicePb.GetCapabilitiesRequest;
  5. import com.google.appengine.api.files.FileServicePb.GetCapabilitiesResponse;
  6. import com.google.appengine.api.files.FileServicePb.GetShuffleStatusRequest;
  7. import com.google.appengine.api.files.FileServicePb.GetShuffleStatusResponse;
  8. import com.google.appengine.api.files.FileServicePb.ShuffleEnums;
  9. import com.google.appengine.api.files.FileServicePb.ShuffleInputSpecification;
  10. import com.google.appengine.api.files.FileServicePb.ShuffleOutputSpecification;
  11. import com.google.appengine.api.files.FileServicePb.ShuffleRequest;
  12. import com.google.appengine.repackaged.com.google.protobuf.InvalidProtocolBufferException;
  13. import com.google.appengine.tools.mapreduce.impl.util.SerializationUtil;
  14. import com.google.apphosting.api.ApiProxy;
  15. import com.google.common.base.Preconditions;
  16. import java.util.List;
  17. import java.util.logging.Logger;
  18. /**
  19. *
  20. * @author ohler@google.com (Christian Ohler)
  21. */
  22. class ShuffleServiceImpl implements ShuffleService {
  23. @SuppressWarnings("unused")
  24. private static final Logger log = Logger.getLogger(ShuffleServiceImpl.class.getName());
  25. @Override
  26. public void shuffle(String shuffleId,
  27. List<AppEngineFile> inputFiles, List<AppEngineFile> outputFiles,
  28. ShuffleCallback callback) {
  29. ShuffleRequest.Builder request = ShuffleRequest.newBuilder()
  30. .setShuffleName(shuffleId);
  31. for (AppEngineFile inputFile : inputFiles) {
  32. // NOTE: This does nothing for blobstore files. It does NOT check for "writable:".
  33. Preconditions.checkArgument(inputFile.isReadable(), "Not readable: %s", inputFile);
  34. request.addInput(
  35. ShuffleInputSpecification.newBuilder()
  36. .setPath(inputFile.getFullPath())
  37. // HACK(ohler): This is supposedly the default but the shuffler
  38. // crashes if we don't specify it.
  39. .setFormat(ShuffleEnums.InputFormat.RECORDS_KEY_VALUE_PROTO_INPUT)
  40. .build());
  41. }
  42. ShuffleOutputSpecification.Builder output = ShuffleOutputSpecification.newBuilder();
  43. // HACK(ohler): This is supposedly the default but let's set it just in case
  44. // the shuffler crashes if we don't (as it does for the input format).
  45. output.setFormat(ShuffleEnums.OutputFormat.RECORDS_KEY_MULTI_VALUE_PROTO_OUTPUT);
  46. for (AppEngineFile outputFile : outputFiles) {
  47. // NOTE: This does nothing for blobstore files. It does NOT check for "writable:".
  48. Preconditions.checkArgument(outputFile.isWritable(), "Not writable: %s", outputFile);
  49. output.addPath(outputFile.getFullPath());
  50. }
  51. request.setOutput(output.build());
  52. // This currently needs to be 0.
  53. request.setShuffleSizeBytes(0);
  54. ShuffleRequest.Callback.Builder callbackProto = ShuffleRequest.Callback.newBuilder()
  55. .setUrl(callback.getUrl())
  56. .setMethod(callback.getMethod());
  57. if (callback.getAppVersionId() != null) {
  58. callbackProto.setAppVersionId(callback.getAppVersionId());
  59. } else {
  60. // Leave unset, or set to ApiProxy.getCurrentEnvironment().getVersionId()?
  61. callbackProto.setAppVersionId(ApiProxy.getCurrentEnvironment().getVersionId());
  62. }
  63. log.info("versionId: " + ApiProxy.getCurrentEnvironment().getVersionId());
  64. if (callback.getQueue() != null) {
  65. callbackProto.setQueue(callback.getQueue());
  66. }
  67. request.setCallback(callbackProto.build());
  68. log.info("Starting shuffle job " + shuffleId + " with callback " + callback
  69. + ": " + request.build());
  70. if (!isAvailable()) {
  71. throw new RuntimeException("not available");
  72. }
  73. ApiProxy.ApiConfig config = new ApiProxy.ApiConfig();
  74. config.setDeadlineInSeconds(30.0);
  75. // TODO(ohler): handle exceptions
  76. byte[] response = ApiProxy.makeSyncCall("file", "Shuffle", request.build().toByteArray(),
  77. config);
  78. // response has no data, no point in parsing it
  79. }
  80. @Override
  81. public boolean isAvailable() {
  82. byte[] responseBytes = ApiProxy.makeSyncCall("file", "GetCapabilities",
  83. GetCapabilitiesRequest.newBuilder().build().toByteArray());
  84. GetCapabilitiesResponse response;
  85. try {
  86. response = GetCapabilitiesResponse.parseFrom(responseBytes);
  87. } catch (InvalidProtocolBufferException e) {
  88. throw new RuntimeException("Failed to parse GetCapabilitiesResponse: "
  89. + SerializationUtil.prettyBytes(responseBytes), e);
  90. }
  91. return response.getShuffleAvailable();
  92. }
  93. // TODO(ohler): Don't use protobuf return type
  94. @Override
  95. public GetShuffleStatusResponse getStatus(String shuffleId) {
  96. byte[] responseBytes = ApiProxy.makeSyncCall("file", "GetShuffleStatus",
  97. GetShuffleStatusRequest.newBuilder().setShuffleName(shuffleId).build().toByteArray());
  98. GetShuffleStatusResponse response;
  99. try {
  100. response = GetShuffleStatusResponse.parseFrom(responseBytes);
  101. } catch (InvalidProtocolBufferException e) {
  102. throw new RuntimeException("Failed to parse GetShuffleStatusResponse: "
  103. + SerializationUtil.prettyBytes(responseBytes), e);
  104. }
  105. return response;
  106. }
  107. }