Package org.nebulaframework.grid.cluster.manager.services.jobs

Source Code of org.nebulaframework.grid.cluster.manager.services.jobs.ClusterJobServiceImpl

/*
* Copyright (C) 2008 Yohan Liyanage.
*
* 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 org.nebulaframework.grid.cluster.manager.services.jobs;

import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nebulaframework.core.job.GridJob;
import org.nebulaframework.core.job.ResultCallback;
import org.nebulaframework.core.job.archive.GridArchive;
import org.nebulaframework.core.job.deploy.GridJobInfo;
import org.nebulaframework.core.job.exceptions.GridJobPermissionDeniedException;
import org.nebulaframework.core.job.exceptions.GridJobRejectionException;
import org.nebulaframework.core.job.future.GridJobFutureServerImpl;
import org.nebulaframework.deployment.classloading.GridArchiveClassLoader;
import org.nebulaframework.deployment.classloading.GridNodeClassLoader;
import org.nebulaframework.deployment.classloading.service.ClassLoadingService;
import org.nebulaframework.grid.cluster.manager.ClusterManager;
import org.nebulaframework.grid.cluster.manager.services.jobs.remote.RemoteClusterJobService;
import org.nebulaframework.grid.cluster.manager.services.jobs.splitaggregate.AggregatorService;
import org.nebulaframework.grid.cluster.manager.services.jobs.splitaggregate.SplitterService;
import org.nebulaframework.grid.cluster.node.GridNodeProfile;
import org.nebulaframework.grid.service.event.ServiceEventsSupport;
import org.nebulaframework.grid.service.event.ServiceHookCallback;
import org.nebulaframework.grid.service.message.ServiceMessage;
import org.nebulaframework.grid.service.message.ServiceMessageType;
import org.nebulaframework.util.hashing.SHA1Generator;
import org.nebulaframework.util.io.IOSupport;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.util.Assert;

/**
* Default implementation of {@code ClusterJobService}. This class allows
* {@code GridNode}s to submit {@code GridJob}s to the {@code ClusterManager},
* and also handles the execution of the {@code GridJobs}, with the support of
* {@code SplitterService} and {@code AggregatorService}.
* <p>
* <i>Spring Managed</i>
*
* @author Yohan Liyanage
* @version 1.0
*
* @see ClusterJobService
* @see SplitterService
* @see AggregatorService
* @see JobServiceJmsSupport
*
*/
public class ClusterJobServiceImpl implements ClusterJobService,
    InternalClusterJobService {

  private static Log log = LogFactory.getLog(ClusterJobServiceImpl.class);
  private static int finished = 0;

  private ClusterManager cluster;
  private JobServiceJmsSupport jmsSupport;

  @SuppressWarnings("unchecked")
  private Map<Class<? extends GridJob>, JobExecutionManager> executors = new HashMap<Class<? extends GridJob>, JobExecutionManager>();

  private RemoteClusterJobService remoteJobServiceProxy;

  // Holds GridJobProfiles of all active GridJobs, against its JobId
  // A LinkedHashMap is used to ensure insertion order iteration
  private Map<String, GridJobProfile> jobs = new LinkedHashMap<String, GridJobProfile>();

  /**
   * Instantiates a ClusterJobServiceImpl for the given {@code ClusterManager}
   * instance.
   *
   * @param cluster
   *            Owner {@code ClusterManager}
   */
  public ClusterJobServiceImpl(ClusterManager cluster) {
    super();
    this.cluster = cluster;
  }

  /**
   * Registers a {@link JobExecutionManager} with this JobExecutionService,
   * which is capable of handling {@code GridJob}s of type {@code clazz}.
   *
   * @param clazz
   *            Type of GridJob Class
   * @param manager
   *            JobExecutionManager
   */

  public void setExecutors(JobExecutionManager[] managers) {
    for (JobExecutionManager manager : managers) {
      executors.put(manager.getInterface(), manager);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String submitJob(UUID owner, String className, byte[] classData)
      throws GridJobRejectionException {
    // Delegate to overloaded version
    return submitJob(owner, className, classData, null, null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String submitJob(UUID owner, String className, byte[] classData,
      GridArchive archive) throws GridJobRejectionException {
    return submitJob(owner, className, classData, archive, null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String submitJob(UUID owner, String className, byte[] classData,
      String resultCallbackQueue) throws GridJobRejectionException {
    return submitJob(owner, className, classData, null, resultCallbackQueue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String submitJob(final UUID owner, final String className,
      final byte[] classData, GridArchive archive,
      String resultCallbackQueue) throws GridJobRejectionException {

    // Create JobId [ClusterID.OwnerID.RandomUUID]
    final String jobId = this.cluster.getClusterId() + "." + owner + "."
        + UUID.randomUUID();

    // Create Infrastructure - JMS Queues
    jmsSupport.createTaskQueue(jobId);
    jmsSupport.createResultQueue(jobId);
    jmsSupport.createFutureQueue(jobId);

    // Create GridJobFuture, which will be used remotely by
    // owner node to monitor / obtain results
    final GridJobFutureServerImpl future = jmsSupport.createFuture(jobId, this);

    // Create GridJobProfile for GridJob
    final GridJobProfile profile = new GridJobProfile();
    profile.setJobId(jobId);
    profile.setOwner(owner);
    profile.setFuture(future);

    ClassLoader classLoader = null;
    if (archive!=null) {
      classLoader = createArchiveClassLoader(archive, owner);
    }
    else {
      classLoader = createNodeClassLoader(owner);
    }
    try {
      // Deserialize and retrieve GridJob
      profile.setJob(getGridJobInstance(owner, classData, archive, classLoader));
    } catch (Exception e) {
      log.warn("[JobService] Unable to de-serialize Job", e);
      throw new GridJobRejectionException("Unable to de-serialize Job", e);
    }

    synchronized (this) {
      // Insert GridJob to active jobs map
      this.jobs.put(jobId, profile);
    }

    if (resultCallbackQueue != null) {
      ResultCallback proxy = jmsSupport.createResultCallbackProxy(jobId, resultCallbackQueue);
      profile.setResultCallback(proxy);
    }

    if (archive != null) {
      // If Job has a GridArchive, verify integrity
      if (!verifyArchive(archive))
        throw new GridJobRejectionException(
            "Archive verification failed");

      // Put Archive into GridJobProfile
      profile.setArchive(archive);
    }

    if (!startGridJob(profile, classLoader)) {
      // Unsupported Type
      throw new GridJobRejectionException("GridJob Type Not Supported : "
          + profile.getJob().getClass().getName());
    }

    // Track to see if Job Submitter Node Fails
    stopIfNodeFails(owner, jobId);

    // Notify Job Start to Workers
    notifyJobStart(jobId);

    return jobId;
  }

  /**
   * De-serializes and returns the {@link GridJob} instance, from the byte[].
   *
   * @param owner
   *            Owner Node (for ClassLoading)
   * @param classData
   *            Serialized data
   * @param archive
   *            GridArchive, can be null
   *
   * @return GridJob instance
   *
   * @throws IOException
   *             if occurred while processing
   * @throws ClassNotFoundException
   *             if a required class definition is missing
   */
  private GridJob<?, ?> getGridJobInstance(final UUID owner,
      final byte[] classData, final GridArchive archive, ClassLoader cl)
      throws IOException, ClassNotFoundException {

    if (archive != null) {
      // Read from Archive if Archived GridJob
      return IOSupport.deserializeFromBytes(classData, cl);
    } else {
      // Read from GridNodeClassLoader
      return (GridJob<?, ?>) IOSupport
          .deserializeFromBytes(classData, cl);
    }
  }

  /**
   * Creates a {@link GridNodeClassLoader} to remotely load necessary class
   * definitions.
   *
   * @param owner
   *            Owner GridNode to load classes from
   *
   * @return ClassLoader instance
   */
  protected ClassLoader createNodeClassLoader(final UUID owner) {
    return AccessController
        .doPrivileged(new PrivilegedAction<ClassLoader>() {

          @Override
          public ClassLoader run() {
            ClassLoadingService service = ClusterManager
                .getInstance().getClassLoadingService();
            return new GridNodeClassLoader(owner, service);
          }

        });
  }

  protected ClassLoader createArchiveClassLoader(final GridArchive archive,
      final UUID owner) {

    return AccessController
        .doPrivileged(new PrivilegedAction<ClassLoader>() {

          @Override
          public ClassLoader run() {
            return new GridArchiveClassLoader(archive,
                createNodeClassLoader(owner));
          }

        });
  }

  /**
   * Starts the given GridJob on the Grid. This method attempts to find a
   * proper {@link JobExecutionManager} for the job, and if found, it
   * delegates to {@link #startExecution(JobExecutionManager, GridJobProfile)}
   * method.
   *
   * @param profile
   *            GridJobProfile for the job
   * @return boolean indicating result
   */
  private boolean startGridJob(GridJobProfile profile, ClassLoader cl) {

    GridJob<?, ?> job = profile.getJob();

    /* -- Check for direct implementation -- */

    // Get all implemented interfaces of GridJob
    Class<?>[] ifaces = job.getClass().getInterfaces();

    for (Class<?> clazz : ifaces) {

      // If we have a executor for the interface
      if (executors.containsKey(clazz)) {

        // Attempt to Start GridJob
        boolean started = startExecution(executors.get(clazz), profile, cl);

        // If Success
        if (started) {
          return true;
        }
      }
    }

    /* -- Check for indirect implementation -- */
    if (findJobClass(job.getClass(), profile, cl)) {
      return true;
    }

    log.error("[JobService Unable to Find JobManager for Job Type "
        + job.getClass().getName());

    // No JobExecutionManager for Job
    return false;
  }

  public boolean findJobClass(Class<?> c, GridJobProfile profile, ClassLoader cl) {

    // If we have a executor for the interface
    if (executors.containsKey(c)) {

      // Attempt to Start GridJob
      boolean started = startExecution(executors.get(c), profile, cl);

      // If Success
      if (started) {
        return true;
      }

      log.debug("Unable to Start GridJobManager for GridJob Type "
          + c.getName());
      return false;
    }

    if (c.getSuperclass() != null) {
      if (findJobClass(c.getSuperclass(), profile, cl)) {
        return true;
      }
    }

    for (Class<?> clazz : c.getInterfaces()) {
      if (findJobClass(clazz, profile, cl)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Starts execution of the given job, using the specified
   * {@link JobExecutionManager}.
   *
   * @param jobExecutionManager
   *            Job execution manager for the job type
   * @param profile
   *            profile of the GridJob
   *
   * @return boolean indicating success / failure
   */
  private boolean startExecution(JobExecutionManager jobExecutionManager,
      GridJobProfile profile, ClassLoader cl) {
    return jobExecutionManager.startExecution(profile, cl);
  }

  /**
   * Stops execution of the given {@code GridJob} if the specified
   * {@code GridNode} fails or leaves the Grid.
   *
   * @param nodeId
   *            {@code GridNode} Id
   * @param jobId
   *            {@code GridJob} Id
   */
  private void stopIfNodeFails(UUID nodeId, final String jobId) {

    ServiceEventsSupport.addServiceHook(
      new ServiceHookCallback() {
        public void onServiceEvent(
            ServiceMessage message) {
          try {
            cancelJob(jobId);
          } catch (IllegalArgumentException e) {
            // Job Already Stopped
          }
        }
      }, nodeId.toString(),
      ServiceMessageType.HEARTBEAT_FAILED,
      ServiceMessageType.NODE_UNREGISTERED);
  }

  /**
   * Verifies the {@code GridArchive} by comparing its provided SHA1 Hash
   * against generated SHA1 Hash for the bytes of the GridArchive.
   *
   * @param archive
   *            {@code GridArchive} to be verified
   * @return if success {@code true}, otherwise {@code false}
   */
  private boolean verifyArchive(GridArchive archive) {
    // Try to compare SHA1 Digests for bytes
    return SHA1Generator.generateAsString(archive.getBytes())
        .equals(archive.getHash());
  }

  /**
   * Implementation of {@link ClusterJobService#requestJob(String)}.
   * <p>
   * {@inheritDoc}
   */
  // FIXME currently allows all nodes to participate
  public GridJobInfo requestJob(String jobId, GridNodeProfile nodeProfile)
      throws GridJobPermissionDeniedException, IllegalArgumentException {

    if (isRemoteClusterJob(jobId)) {
      log.debug("[ClusterJobService] Remote Job Request {" + jobId + "}");
      return remoteJobServiceProxy.remoteJobRequest(jobId, nodeProfile);
    }

    log.debug("[ClusterJobService] Local Job Request {" + jobId + "}");

    try {
      // Get Profile
      GridJobProfile profile = jobs.get(jobId);

      // If no Job found
      if (profile == null) {
        log.debug("[ClusterJobService] JobId " + jobId
            + " not in Jobs Collection of Cluster");
        throw new NullPointerException("Job Not Found");
      }

      if (!profile.processRequest(nodeProfile)) {
        throw new GridJobPermissionDeniedException("Permission Denied");
      }

      // Return GridJobInfo for Profile
      return createInfo(profile);

    } catch (NullPointerException ex) {
      throw new IllegalArgumentException("Invalid GridJob Id " + jobId);
    } catch (Exception e) {
      throw new GridJobPermissionDeniedException(
          "Permission denied due to exception", e);
    }
  }

  /**
   * Returns {@code true} if the passed JobId indicates a remote
   * {@code GridJob}, that is, a {@code GridJob} of another Cluster. This
   * method parses the given {@code JobId} to extract the ClusterID portion of
   * it to identify the originating cluster.
   *
   * @param jobId
   *            JobId to check
   * @return true if remote job, false otherwise
   * @throws IllegalArgumentException
   *             if {@code jobId} is not valid
   */
  private boolean isRemoteClusterJob(String jobId)
      throws IllegalArgumentException {

    String jobClusterId = jobId.split("\\.")[0];
    return !(this.cluster.getClusterId().equals(UUID
        .fromString(jobClusterId)));
  }

  /**
   * {@inheritDoc}
   */
  public GridJobInfo requestNextJob(GridNodeProfile nodeProfile) {

    GridJobProfile profile = null;

    // For each job, try to get permission
    for (GridJobProfile p : jobs.values()) {

      // If Allowed to Participate
      if (p.processRequest(nodeProfile)) {
        profile = p;
        break;
      }
    }

    // If job is available, return profile, or else null
    return (profile != null) ? createInfo(profile) : null;
  }

  /**
   * Creates and returns the {@code GridJobInfo} instance for a
   * {@code GridJob}, denoted by the {@code GridJobProfile}.
   *
   * @param profile
   *            {@code GridJobProfile} for Job
   *
   * @return The {@code GridJobInfo} for the Job
   */
  protected GridJobInfo createInfo(GridJobProfile profile) {

    // Check for Nulls
    Assert.notNull(profile);

    GridJobInfo info = new GridJobInfo(profile.getJobId(), profile.getJob()
        .getClass().getSimpleName());

    if (profile.isArchived()) {
      // If Archived Job, include Archive
      info.setArchive(profile.getArchive());
    }
    return info;
  }

  /**
   * Cancels execution of the given {@code GridJob} on the Grid.
   *
   * @param jobId
   *            JobId of the GridJob to be canceled
   * @return a {@code boolean} value indicating success ({@code true}) /
   *         failure ({@code false}).
   */
  public boolean cancelJob(String jobId) throws IllegalArgumentException {

    // Check JobId
    if (!this.jobs.containsKey(jobId)) {
      throw new IllegalArgumentException(
          "Invalid JobId, not an active Job of this Cluster");
    }

    GridJobProfile profile = jobs.get(jobId);

    log.debug("[ClusterJobService] Cancelling Job : {" + profile.getJobId()
        + "}");

    notifyJobCancel(jobId);
    return profile.cancel();
  }

  /**
   * Notifies that a Job has started to all nodes in this cluster.
   *
   * @param jobId
   *            JobId of started Job.
   */
  protected void notifyJobStart(String jobId) {

    log.info("[JobService] Starting GridJob " + jobId);

    // Create ServiceMessage for Job Start Notification
    ServiceMessage message = new ServiceMessage(jobId,
        ServiceMessageType.JOB_START);

    // Send ServiceMessage to GridNodes
    cluster.getServiceMessageSender().sendServiceMessage(message);
    log.debug("[ClusterJobService] Notified Job Start {" + jobId + "}");
  }

  /**
   * Notifies to GridNodes that a particular GridJob has finished execution.
   *
   * @param jobId
   *            JobId of the finished GridJob
   */
  public void notifyJobEnd(String jobId) {

    finished++;

    // Remove GridJob from Active GridJobs map
    removeJob(jobId);

    // Create ServiceMessage for Job End Notification
    ServiceMessage message = new ServiceMessage(jobId,
        ServiceMessageType.JOB_END);

    // Send ServiceMessage to GridNodes
    cluster.getServiceMessageSender().sendServiceMessage(message);
    log.debug("[ClusterJobService] Notified Job End {" + jobId + "}");

    log.info("[JobService] Finished GridJob " + jobId);

  }

  /**
   * Notifies to GridNodes that a particular GridJob has been canceled.
   *
   * @param jobId
   *            JobId of the canceled GridJob.
   */
  public void notifyJobCancel(String jobId) {

    finished++;
    // Remove GridJob from Active GridJobs map
    removeJob(jobId);

    // Create ServiceMessage for Job Cancel Notification
    ServiceMessage message = new ServiceMessage(jobId,
        ServiceMessageType.JOB_CANCEL);

    // Send ServiceMessage to GridNodes
    cluster.getServiceMessageSender().sendServiceMessage(message);
    log.debug("[ClusterJobService] Notified Job Cancel {" + jobId + "}");

    log.info("[JobService] Cancelled GridJob " + jobId);

  }

  /**
   * Removes a given {@code GridJob} from the active GridJobs collection of
   * this service.
   *
   * @param jobId
   *            JobId of the GridJob to remove from collection
   */
  protected synchronized void removeJob(String jobId) {
    this.jobs.remove(jobId);
  }

  /**
   * Returns the {@code GridJobProfile} for a given {@code GridJob}.
   *
   * @param jobId
   *            JobId of the {@code GridJob}
   * @return {@code GridJobProfile} for the specified {@code GridJob}.
   */
  public synchronized GridJobProfile getProfile(String jobId) {
    if (jobs.containsKey(jobId)) {
      return jobs.get(jobId);
    } else {
      throw new IllegalArgumentException("GridJob does not exist "
          + jobId);
    }
  }

  /**
   * Returns a {@code boolean} value indicating whether a given JobId refers
   * to an active {@code GridJob} of this service instance.
   *
   * @param jobId
   *            JobId of the {@code GridJob}
   *
   * @return {@code true} if the {@code GridJob} is active, {@code false}
   *         otherwise.
   */
  public synchronized boolean isActiveJob(String jobId) {
    return this.jobs.containsKey(jobId);
  }

  /**
   * Sets the {@code JobServiceJmsSupport} instance for this service.
   * <p>
   * {@code JobServicesJmsSupport} provides support methods which handles JMS
   * specific activities in Job handling, such as creation of JMS
   * {@code Queues}, etc.
   * <p>
   * <b>Note : </b>This is a <b>required</b> dependency.
   * <p>
   * <i>Spring Injected</i>
   *
   * @param jmsSupport
   *            {@code JobServiceJmsSupport} instance
   */
  @Required
  public void setJmsSupport(JobServiceJmsSupport jmsSupport) {
    this.jmsSupport = jmsSupport;
  }

  /**
   * Sets the {@code RemoteClusterJobService} proxy to be used by the
   * {@code ClusterManager}.
   * <p>
   * <b>Note : </b>This is a <b>required</b> dependency.
   * <p>
   * <i>Spring Injected</i>
   *
   * @param remoteJobServiceProxy
   *            proxy
   */
  @Required
  public void setRemoteJobServiceProxy(
      RemoteClusterJobService remoteJobServiceProxy) {
    this.remoteJobServiceProxy = remoteJobServiceProxy;
  }

  /**
   * Returns the number of jobs which have finished execution on this Cluster.
   *
   * @return finished job count
   */
  public int getFinishedJobCount() {
    return finished;
  }

  /**
   * Returns the number of currently active GridJobs on this Cluster.
   *
   * @return active job count
   */
  public int getActiveJobCount() {
    return jobs.size();
  }

}
TOP

Related Classes of org.nebulaframework.grid.cluster.manager.services.jobs.ClusterJobServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.