
com.hazelcast.jet.impl.JetClientInstanceImpl Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.
*/
package com.hazelcast.jet.impl;
import com.hazelcast.client.impl.client.DistributedObjectInfo;
import com.hazelcast.client.impl.clientside.HazelcastClientInstanceImpl;
import com.hazelcast.client.impl.protocol.ClientMessage;
import com.hazelcast.client.impl.protocol.codec.ClientGetDistributedObjectsCodec;
import com.hazelcast.client.impl.protocol.codec.JetExistsDistributedObjectCodec;
import com.hazelcast.client.impl.protocol.codec.JetGetJobAndSqlSummaryListCodec;
import com.hazelcast.client.impl.protocol.codec.JetGetJobIdsCodec;
import com.hazelcast.client.impl.protocol.codec.JetGetJobSummaryListCodec;
import com.hazelcast.client.impl.protocol.codec.JetUploadJobMetaDataCodec;
import com.hazelcast.client.impl.protocol.codec.JetUploadJobMultipartCodec;
import com.hazelcast.client.impl.spi.impl.ClientInvocation;
import com.hazelcast.internal.serialization.Data;
import com.hazelcast.internal.serialization.SerializationService;
import com.hazelcast.internal.util.Sha256Util;
import com.hazelcast.jet.JetException;
import com.hazelcast.jet.JetService;
import com.hazelcast.jet.Job;
import com.hazelcast.jet.config.JetConfig;
import com.hazelcast.jet.config.JobConfig;
import com.hazelcast.jet.impl.operation.GetJobIdsOperation.GetJobIdsResult;
import com.hazelcast.jet.impl.submitjob.clientside.execute.JobExecuteCall;
import com.hazelcast.jet.impl.submitjob.clientside.upload.JobUploadCall;
import com.hazelcast.jet.impl.submitjob.clientside.validator.SubmitJobParametersValidator;
import com.hazelcast.jet.impl.util.ExceptionUtil;
import com.hazelcast.jet.pipeline.Pipeline;
import com.hazelcast.logging.ILogger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.security.auth.Subject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import static com.hazelcast.internal.util.ExceptionUtil.sneakyThrow;
import static com.hazelcast.jet.impl.operation.GetJobIdsOperation.ALL_JOBS;
import static com.hazelcast.jet.impl.util.ExceptionUtil.rethrow;
/**
* Client-side {@code JetInstance} implementation
*/
public class JetClientInstanceImpl extends AbstractJetInstance {
private final HazelcastClientInstanceImpl client;
private final SerializationService serializationService;
public JetClientInstanceImpl(HazelcastClientInstanceImpl hazelcastInstance) {
super(hazelcastInstance);
this.client = hazelcastInstance;
this.serializationService = client.getSerializationService();
ExceptionUtil.registerJetExceptions(hazelcastInstance.getClientExceptionFactory());
}
@Override
public UUID getMasterId() {
return client.getClientClusterService().getMasterMember().getUuid();
}
@Override
public Map getJobsInt(String onlyName, Long onlyJobId) {
return invokeRequestOnAnyMemberAndDecodeResponse(
JetGetJobIdsCodec.encodeRequest(onlyName, onlyJobId == null ? ALL_JOBS : onlyJobId),
resp -> {
Data responseSerialized = JetGetJobIdsCodec.decodeResponse(resp).response;
return serializationService.toObject(responseSerialized);
});
}
@Nonnull
@Override
public JetConfig getConfig() {
throw new UnsupportedOperationException("Jet Configuration is not available on the client");
}
/**
* Returns a list of jobs and a summary of their details.
* @deprecated Since 5.3, to be removed in 6.0. Use {@link #getJobAndSqlSummaryList()} instead
*/
@Nonnull
@Deprecated
public List getJobSummaryList() {
return invokeRequestOnMasterAndDecodeResponse(JetGetJobSummaryListCodec.encodeRequest(),
JetGetJobSummaryListCodec::decodeResponse);
}
/**
* Returns a list of jobs and a summary of their details (including SQL ones).
*/
@Nonnull
public List getJobAndSqlSummaryList() {
return invokeRequestOnMasterAndDecodeResponse(JetGetJobAndSqlSummaryListCodec.encodeRequest(),
JetGetJobAndSqlSummaryListCodec::decodeResponse);
}
@Nonnull
public HazelcastClientInstanceImpl getHazelcastClient() {
return client;
}
@Override
public boolean existsDistributedObject(@Nonnull String serviceName, @Nonnull String objectName) {
return invokeRequestOnAnyMemberAndDecodeResponse(
JetExistsDistributedObjectCodec.encodeRequest(serviceName, objectName),
JetExistsDistributedObjectCodec::decodeResponse
);
}
public List getDistributedObjects() {
return invokeRequestOnAnyMemberAndDecodeResponse(
ClientGetDistributedObjectsCodec.encodeRequest(),
ClientGetDistributedObjectsCodec::decodeResponse
);
}
@Override
public Job newJobProxy(long jobId, UUID lightJobCoordinator) {
return new ClientJobProxy(client, jobId, lightJobCoordinator);
}
@Override
public Job newJobProxy(long jobId,
boolean isLightJob,
@Nonnull Object jobDefinition,
@Nonnull JobConfig config,
@Nullable Subject subject) {
if (subject != null) {
throw new UnsupportedOperationException("Submitting a job with subject is not allowed for client");
}
return new ClientJobProxy(client, jobId, isLightJob, jobDefinition, config);
}
/**
* For the client side, the jar is uploaded to a random cluster member and then this member runs the main method to
* start the job.
* The jar should have a main method that submits a job with {@link #newJob(Pipeline)} or
* {@link #newLightJob(Pipeline)} methods
*
* The upload operation is performed in parts to avoid OOM exceptions on the client and member.
* For Java clients the part size is controlled by {@link com.hazelcast.client.properties.ClientProperty#JOB_UPLOAD_PART_SIZE}
* property
*
* Limitations for the member side jobs:
*
* - The job can only access resources on the member or cluster. This is different from the jobs submitted from
* the hz-cli tool. A job submitted from hz-cli tool creates a local HazelcastInstance on the client JVM and
* connects to cluster. Therefore, the job can access local resources. This is not the case for the jar
* uploaded to a member.
*
*
*
* Note : Job submission and job startup are two different things.
* The job submission is a synchronous process but job startup is an asynchronous process. This call can only detect
* failures during job submission. If you want to check for job startup failures, you need may use one of these
* methods and check for Job state
*
* - {@link JetService#getJob(String)}
* - {@link JetService#getJobs()}
*
* @throws JetException on submission error.
*/
public void submitJobFromJar(@Nonnull SubmitJobParameters submitJobParameters) {
if (submitJobParameters.isJarOnMember()) {
executeJobFromJar(submitJobParameters);
} else {
uploadJobFromJar(submitJobParameters);
}
}
private void executeJobFromJar(@Nonnull SubmitJobParameters submitJobParameters) {
try {
SubmitJobParametersValidator.validateJarOnMember(submitJobParameters);
Path jarPath = submitJobParameters.getJarPath();
JobExecuteCall jobExecuteCall = initializeJobExecuteCall(submitJobParameters.getJarPath());
// Send only job metadata
getLogger().fine("Submitting JobMetaData for jarPath: %s", jarPath);
sendJobMetaDataForExecute(jobExecuteCall, submitJobParameters);
getLogger().fine("Job execution from jar '%s' finished successfully", jarPath);
} catch (Exception exception) {
sneakyThrow(exception);
}
}
private void uploadJobFromJar(@Nonnull SubmitJobParameters submitJobParameters) {
try {
SubmitJobParametersValidator.validateJarOnClient(submitJobParameters);
Path jarPath = submitJobParameters.getJarPath();
JobUploadCall jobUploadCall = initializeJobUploadCall(submitJobParameters.getJarPath());
// First send job metadata
getLogger().fine("Submitting JobMetaData for jarPath: %s", jarPath);
sendJobMetaDataForUpload(jobUploadCall, submitJobParameters);
// Then send job parts
sendJobMultipart(jobUploadCall, jarPath);
getLogger().fine("Job upload from jar '%s' finished successfully", jarPath);
} catch (IOException | NoSuchAlgorithmException exception) {
sneakyThrow(exception);
}
}
// This method is public for testing purposes.
public JobUploadCall initializeJobUploadCall(Path jarPath)
throws IOException, NoSuchAlgorithmException {
JobUploadCall jobUploadCall = new JobUploadCall();
jobUploadCall.initializeJobUploadCall(client, jarPath);
return jobUploadCall;
}
public JobExecuteCall initializeJobExecuteCall(Path jarPath) {
JobExecuteCall jobExecuteCall = new JobExecuteCall();
jobExecuteCall.initializeJobExecuteCall(client, jarPath);
return jobExecuteCall;
}
private void sendJobMetaDataForUpload(JobUploadCall jobUploadCall,
SubmitJobParameters submitJobParameters) {
ClientMessage jobMetaDataRequest = JetUploadJobMetaDataCodec.encodeRequest(
jobUploadCall.getSessionId(),
submitJobParameters.isJarOnMember(),
jobUploadCall.getFileNameWithoutExtension(),
jobUploadCall.getSha256HexOfJar(),
submitJobParameters.getSnapshotName(),
submitJobParameters.getJobName(),
submitJobParameters.getMainClass(),
submitJobParameters.getJobParameters());
invokeRequestNoRetryOnRandom(jobUploadCall.getMemberUuid(), jobMetaDataRequest);
}
private void sendJobMetaDataForExecute(JobExecuteCall jobExecuteCall, SubmitJobParameters submitJobParameters) {
ClientMessage jobMetaDataRequest = JetUploadJobMetaDataCodec.encodeRequest(
jobExecuteCall.getSessionId(),
submitJobParameters.isJarOnMember(),
jobExecuteCall.getJarPath(),
jobExecuteCall.getSha256HexOfJar(),
submitJobParameters.getSnapshotName(),
submitJobParameters.getJobName(),
submitJobParameters.getMainClass(),
submitJobParameters.getJobParameters());
invokeRequestNoRetryOnRandom(jobExecuteCall.getMemberUuid(), jobMetaDataRequest);
}
private void sendJobMultipart(JobUploadCall jobUploadCall, Path jarPath)
throws IOException, NoSuchAlgorithmException {
File file = jarPath.toFile();
byte[] partBuffer = jobUploadCall.allocatePartBuffer();
try (FileInputStream fileInputStream = new FileInputStream(file)) {
// Start from part #1
for (int currentPartNumber = 1; currentPartNumber <= jobUploadCall.getTotalParts(); currentPartNumber++) {
int bytesRead = fileInputStream.read(partBuffer);
byte[] dataToSend = jobUploadCall.getDataToSend(partBuffer, bytesRead);
String sha256Hex = Sha256Util.calculateSha256Hex(dataToSend);
//Send the part
ClientMessage jobMultipartRequest = JetUploadJobMultipartCodec.encodeRequest(
jobUploadCall.getSessionId(),
currentPartNumber,
jobUploadCall.getTotalParts(), dataToSend, bytesRead, sha256Hex);
getLogger().fine("Submitting Job Part for jarPath: %s PartNumber %d/%d",
jarPath, currentPartNumber, jobUploadCall.getTotalParts());
invokeRequestNoRetryOnRandom(jobUploadCall.getMemberUuid(), jobMultipartRequest);
}
}
}
@Override
public ILogger getLogger() {
return client.getLoggingService().getLogger(getClass());
}
private S invokeRequestOnMasterAndDecodeResponse(ClientMessage request,
Function decoder) {
return invokeRequestAndDecodeResponse(getMasterId(), request, decoder);
}
private S invokeRequestOnAnyMemberAndDecodeResponse(ClientMessage request,
Function decoder) {
return invokeRequestAndDecodeResponse(null, request, decoder);
}
// Do not retry on random member if invocation fails
private void invokeRequestNoRetryOnRandom(UUID uuid, ClientMessage request) {
ClientInvocation invocation = new ClientInvocation(client, request, null, uuid);
invocation.disallowRetryOnRandom();
invoke(invocation);
}
// Retry on random member if invocation fails
private S invokeRequestAndDecodeResponse(UUID uuid, ClientMessage request,
Function decoder) {
ClientInvocation invocation = new ClientInvocation(client, request, null, uuid);
return invoke(decoder, invocation);
}
private S invoke(Function decoder, ClientInvocation invocation) {
try {
ClientMessage response = invocation.invoke().get();
return serializationService.toObject(decoder.apply(response));
} catch (Exception exception) {
throw rethrow(exception);
}
}
private void invoke(ClientInvocation invocation) {
try {
invocation.invoke().get();
} catch (Exception exception) {
throw rethrow(exception);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy