/**
* Copyright 2015 Otto (GmbH & Co KG)
*
* 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.ottogroup.bi.spqr.resman.node;
import java.io.IOException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.ottogroup.bi.spqr.exception.RemoteClientConnectionFailedException;
import com.ottogroup.bi.spqr.exception.RequiredInputMissingException;
import com.ottogroup.bi.spqr.node.resource.pipeline.MicroPipelineInstantiationResponse;
import com.ottogroup.bi.spqr.node.resource.pipeline.MicroPipelineShutdownResponse;
import com.ottogroup.bi.spqr.node.resource.pipeline.MicroPipelineShutdownResponse.MicroPipelineShutdownState;
import com.ottogroup.bi.spqr.pipeline.MicroPipelineConfiguration;
import com.ottogroup.bi.spqr.pipeline.MicroPipelineValidationResult;
import com.ottogroup.bi.spqr.pipeline.MicroPipelineValidator;
/**
* Communication client used for accessing remote processing nodes for issuing pipeline instantiations
* or requesting statistics
* @author mnxfst
* @since Apr 13, 2015
* TODO implement method for retrieving "alive" state from node
* TODO implement method for retrieving statistics from node
*/
public class SPQRNodeClient {
/** our faithful logging service .... ;-) */
private static final Logger logger = Logger.getLogger(SPQRNodeClient.class);
/** client to be used for accessing remote processing node */
private final Client restClient;
/** base url for accessing service api */
private final String processingNodeServiceBaseUrl;
/** base url for accessing admin api */
private final String processingNodeAdminBaseUrl;
/** pipeline configuration validator */
private final MicroPipelineValidator pipelineConfigurationValidator = new MicroPipelineValidator();
/**
* Initializes the node client using the provided input
* @param protocol
* @param remoteHost
* @param servicePort
* @param adminPort
* @param client
* @throws RequiredInputMissingException
*/
public SPQRNodeClient(final String protocol, final String remoteHost, final int servicePort, final int adminPort, final Client client) throws RequiredInputMissingException {
///////////////////////////////////////////////////////////////
// validate input
if(StringUtils.isBlank(protocol))
throw new RequiredInputMissingException("Missing required protocol");
if(StringUtils.isBlank(remoteHost))
throw new RequiredInputMissingException("Missing required remote host");
if(servicePort < 1)
throw new RequiredInputMissingException("Missing required service port");
if(adminPort < 1)
throw new RequiredInputMissingException("Missing required admin port");
if(client == null)
throw new RequiredInputMissingException("Missing required client");
//
///////////////////////////////////////////////////////////////
this.processingNodeServiceBaseUrl = new StringBuffer(protocol).append("://").append(remoteHost).append(":").append(servicePort).toString();
this.processingNodeAdminBaseUrl = new StringBuffer(protocol).append("://").append(remoteHost).append(":").append(adminPort).toString();
this.restClient = client;
if(logger.isDebugEnabled())
logger.debug("rest client[protocol="+protocol+", host="+remoteHost+", servicePort="+servicePort+", adminPort="+adminPort+"]");
}
/**
* Instantiates the referenced pipeline on the remote processing node
* @param pipelineConfiguration
* @return
* @throws RequiredInputMissingException
* @throws IOException
* @throws RemoteClientConnectionFailedException
*/
public MicroPipelineInstantiationResponse instantiatePipeline(final MicroPipelineConfiguration pipelineConfiguration)
throws IOException, RemoteClientConnectionFailedException {
///////////////////////////////////////////////////////
// validate input for not being null as the content
MicroPipelineValidationResult cfgValidationResult = this.pipelineConfigurationValidator.validate(pipelineConfiguration);
if(cfgValidationResult != MicroPipelineValidationResult.OK)
return new MicroPipelineInstantiationResponse("", cfgValidationResult, "Failed to validate pipeline configuration");
if(StringUtils.isBlank(pipelineConfiguration.getId()))
return new MicroPipelineInstantiationResponse("", MicroPipelineValidationResult.MISSING_PIPELINE_ID, "Failed to generated unique pipeline identifier");
//
///////////////////////////////////////////////////////
StringBuffer url = new StringBuffer(this.processingNodeServiceBaseUrl).append("/pipelines");
if(logger.isDebugEnabled())
logger.debug("Instantiating pipeline [id="+pipelineConfiguration.getId()+"] on processing node " + url.toString());
try {
final WebTarget webTarget = this.restClient.target(url.toString());
return webTarget.request(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(Entity.entity(pipelineConfiguration, MediaType.APPLICATION_JSON), MicroPipelineInstantiationResponse.class);
} catch(Exception e) {
throw new RemoteClientConnectionFailedException("Failed to establish a connection with the remote resource manager [url="+url.toString()+"]. Error: " + e.getMessage());
}
}
/**
* Instantiates or updates the referenced pipeline on the remote processing node
* @param pipelineConfiguration
* @return
* @throws RequiredInputMissingException
* @throws IOException
* @throws RemoteClientConnectionFailedException
*/
public MicroPipelineInstantiationResponse updatePipeline(final MicroPipelineConfiguration pipelineConfiguration)
throws IOException, RemoteClientConnectionFailedException {
///////////////////////////////////////////////////////
// validate input
MicroPipelineValidationResult cfgValidationResult = this.pipelineConfigurationValidator.validate(pipelineConfiguration);
if(cfgValidationResult != MicroPipelineValidationResult.OK)
return new MicroPipelineInstantiationResponse("", cfgValidationResult, "Failed to validate pipeline configuration");
if(StringUtils.isBlank(pipelineConfiguration.getId()))
return new MicroPipelineInstantiationResponse("", MicroPipelineValidationResult.MISSING_PIPELINE_ID, "Failed to generated unique pipeline identifier");
//
///////////////////////////////////////////////////////
StringBuffer url = new StringBuffer(this.processingNodeServiceBaseUrl).append("/pipelines/").append(pipelineConfiguration.getId());
if(logger.isDebugEnabled())
logger.debug("Updating or instantiating pipeline [id="+pipelineConfiguration.getId()+"] on processing node " + url.toString());
try {
final WebTarget webTarget = this.restClient.target(url.toString());
return webTarget.request(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).put(Entity.entity(pipelineConfiguration, MediaType.APPLICATION_JSON), MicroPipelineInstantiationResponse.class);
} catch(Exception e) {
throw new RemoteClientConnectionFailedException("Failed to establish a connection with the remote resource manager [url="+url.toString()+"]. Error: " + e.getMessage());
}
}
/**
* Shuts down the referenced pipeline on the remote processing node
* @param pipelineId
* @return
* @throws RequiredInputMissingException
* @throws RemoteClientConnectionFailedException
*/
public MicroPipelineShutdownResponse shutdown(final String pipelineId) throws RemoteClientConnectionFailedException {
///////////////////////////////////////////////////////
// validate input
if(StringUtils.isBlank(pipelineId))
return new MicroPipelineShutdownResponse(pipelineId, MicroPipelineShutdownState.PIPELINE_ID_MISSING, "Missing required pipeline identifier");
//
///////////////////////////////////////////////////////
StringBuffer url = new StringBuffer(this.processingNodeServiceBaseUrl).append("/pipelines/").append(pipelineId);
if(logger.isDebugEnabled())
logger.debug("Deleting pipeline [id="+pipelineId+"] on processing node " + url.toString());
try {
final WebTarget webTarget = this.restClient.target(url.toString());
return webTarget.request(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).delete(MicroPipelineShutdownResponse.class);
} catch(Exception e) {
throw new RemoteClientConnectionFailedException("Failed to establish a connection with the remote resource manager [url="+url.toString()+"]. Error: " + e.getMessage());
}
}
/**
* Called by {@link SPQRNodeManager} on client de-registration
*/
public void shutdown() {
}
protected String getProcessingNodeServiceBaseUrl() {
return processingNodeServiceBaseUrl;
}
protected String getProcessingNodeAdminBaseUrl() {
return processingNodeAdminBaseUrl;
}
}