Package org.apache.uima.ducc.sm

Source Code of org.apache.uima.ducc.sm.ServiceSet

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.apache.uima.ducc.sm;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.uima.UIMAFramework;
import org.apache.uima.ducc.cli.IServiceApi.RegistrationOption;
import org.apache.uima.ducc.cli.IUiOptions.UiOption;
import org.apache.uima.ducc.cli.UimaAsPing;
import org.apache.uima.ducc.cli.UimaAsServiceMonitor;
import org.apache.uima.ducc.common.IServiceStatistics;
import org.apache.uima.ducc.common.TcpStreamHandler;
import org.apache.uima.ducc.common.utils.DuccLogger;
import org.apache.uima.ducc.common.utils.DuccProperties;
import org.apache.uima.ducc.common.utils.id.ADuccId;
import org.apache.uima.ducc.common.utils.id.DuccId;
import org.apache.uima.ducc.transport.event.common.IDuccState.JobState;
import org.apache.uima.ducc.transport.event.sm.IServiceDescription;
import org.apache.uima.ducc.transport.event.sm.ServiceDescription;
import org.apache.uima.util.Level;


/**
* Represents the collection of process, jobs, and such that implement a given service.
*/

public class ServiceSet
  implements SmConstants
{
    /**
   *
   */
  private static final long serialVersionUID = 1L;

  private DuccLogger logger = DuccLogger.getLogger(this.getClass().getName(), COMPONENT_NAME)

    // key is unique id of descriptor.  The descriptor inherites key from a Job's DuccId, or from
    // a unique-to SM key for implicit references.
    HashMap<DuccId, JobState> implementors = new HashMap<DuccId, JobState>();

    // key is job/service id, value is same.  it's a map for fast existence check
    HashMap<DuccId, DuccId> references = new HashMap<DuccId, DuccId>();

    // For a registered service, here is my registered id
    DuccId id;
    HashMap<Long, DuccId> friendly_ids = new HashMap<Long, DuccId>();
    String history_key = "work-instances";

    // incoming nodes, for dup checking
    List<ServiceSet> predecessors = new ArrayList<ServiceSet>();
    List<ServiceSet> successors   = new ArrayList<ServiceSet>();

    // for UIMA-AS this is the endpoint as the unique identifier of this service
    String key;

    // UIMA-AS endpoint information
    String endpoint;
    String broker;
    String broker_host;
    int broker_port;
    int broker_jmx_port = 1099;
    String[] independentServices = null;

    // Registered services, the submitter
    String user;

    // Registered services, Automatically start at boot, and keep implementors alive
    boolean autostart = false;
    // Registered services, we've been stopped.  Must remember in order to counteract autostart.
    boolean stopped   = false;
    // Registered services, remember if was started by reference only so we can stop when refs die
    boolean referenced_start = false;

    // Registered services, the number of instances to maintain
    int instances = 1;

    // Service pinger
    IServiceMeta serviceMeta = null;

    // After stopping a pinger we need to discard it,and we usually need to stop it well before
    // it is ok to delete residual state such as UIMA-AS queues.  Instead of discarding it, we
    // stash it here to use once the last implementor seems to be dead.
    IServiceMeta residualMeta = null;

    // registered services state files
    DuccProperties job_props  = null;
    DuccProperties meta_props = null;
    String props_filename = null;
    String meta_filename = null;
    boolean deregistered = false;

    ServiceType  service_type  = ServiceType.Undefined;
    ServiceClass service_class = ServiceClass.Undefined;
    ServiceState service_state = ServiceState.Undefined;;

    // structures to manage service linger after it exits
    Timer timer = null;
    LingerTask linger = null;
    long linger_time = 5000;

    int failure_max = ServiceManagerComponent.failure_max;
    int failure_run = 0;                   // max allowed consecutive failures, current failure count

    //JobState     job_state     = JobState.Undefined;
    //
    // This is the constructor for an implicit service
    //
    public ServiceSet(String key, DuccId id)
    {
        this.key = key;
        this.id = id;
        parseEndpoint(key);

        if ( this.service_type == ServiceType.Custom ) {
            throw new IllegalStateException("Custom services may not be referenced as Implicit services.");
        }

        this.service_type = ServiceType.UimaAs;
        this.service_class = ServiceClass.Implicit;

        String state_dir = System.getProperty("DUCC_HOME") + "/state";

        // Need job props and meta props for webserver.
        // The pinger is always the default configured UIMA-AS pinger.
        //
        // job props: working_directory, log_directory
        // meta props: endpoint, user
        job_props = new DuccProperties();
        // job_props.put("working_directory", System.getProperty("user.dir")); // whatever my current dir is
        // job_props.put("log_directory", System.getProperty("user.dir") + "/../logs");
        //job_props.put("service_ping_jvm_args", "-Xmx50M");
        props_filename = state_dir + "/services/" + id.toString() + ".svc";
        saveServiceProperties();

        meta_props = new DuccProperties();
        meta_props.put("user", System.getProperty("user.name"));
        meta_props.put("endpoint", key);
        meta_props.put("service-class", ""+service_class.decode());
        meta_props.put("service-type", ""+service_type.decode());
        meta_props.put("stopped", ""+stopped);
        meta_props.put("service-state", ""+getServiceState());
        meta_props.put("ping-active", "false");
        meta_props.put("service-alive",      "false");
        meta_props.put("service-healthy",    "false");
        meta_props.put("service-statistics", "N/A");
        meta_props.put("ping-only", "true");

        meta_filename = state_dir + "/services/" + id.toString() + ".meta";
        saveMetaProperties();
    }

    //
    // Constructor for a submitted service
    //
    public ServiceSet(DuccId id, DuccId first_implementor, String key, String[] independentServices)
    {
        this.key = key;
        this.id = id;
        this.friendly_ids.put(first_implementor.getFriendly(), null);

        this.independentServices = independentServices;
        this.service_class = ServiceClass.Submitted;       

        parseEndpoint(key);

        String state_dir = System.getProperty("DUCC_HOME") + "/state";
        // Need job props and meta props for webserver.
        // The pinger is always the default configured UIMA-AS pinger.
        // Submitted services must always be UIMA-AS services, for now, checked in caller.
        //
        // job props: working_directory, log_directory
        // meta props: endpoint, user
        job_props = new DuccProperties();
        //job_props.put("service_ping_jvm_args", "-Xmx50M");
        props_filename = state_dir + "/services/" + id.toString() + ".svc";
        saveServiceProperties();

        meta_props = new DuccProperties();
        meta_props.put("user", System.getProperty("user.name"));
        meta_props.put("endpoint", key);
        meta_props.put("service-class", ""+service_class.decode());
        meta_props.put("service-type", ""+service_type.decode());
        meta_props.put("stopped", ""+stopped);
        meta_props.put("service-state", ""+getServiceState());
        meta_props.put("ping-active", "false");
        meta_props.put("service-alive",      "false");
        meta_props.put("service-healthy",    "false");
        meta_props.put("service-statistics", "N/A");
        meta_props.put("implementors", ""+id.getFriendly());
        meta_props.put("ping-only", "false");

        meta_filename = state_dir + "/services/" + id.toString() + ".meta";
        saveMetaProperties();
    }

    //
    // Constructor for a registered service
    //
    public ServiceSet(DuccId id, String props_filename, String meta_filename, DuccProperties props, DuccProperties meta)
    {
        this.job_props = props;
        this.meta_props = meta;
        this.id = id;
        this.props_filename = props_filename;
        this.meta_filename = meta_filename;
        this.service_state = ServiceState.NotAvailable;
        this.linger_time = props.getLongProperty(RegistrationOption.ServiceLinger.decode(), 5000);
        this.key = meta.getProperty("endpoint");
        this.failure_max = props.getIntProperty(UiOption.InstanceFailuresLimit.pname(), ServiceManagerComponent.failure_max);

        parseEndpoint(key);

        this.user = meta.getProperty("user");
        this.instances = meta.getIntProperty("instances", 1);
        this.autostart = meta.getBooleanProperty("autostart", false);

        String idprop  = meta.getProperty("implementors", null);
        if ( idprop != null ) {
            String[] ids = idprop.split("\\s");
            for ( String i : ids ) {
                friendly_ids.put(Long.parseLong(i), null);
            }
        }
        this.service_class = ServiceClass.Registered;

        parseIndependentServices();

        if ( ! job_props.containsKey("service_ping_dolog")) {
            job_props.put("service_ping_dolog", "false");
        }       
        if ( !job_props.containsKey("service_ping_timeout") ) {
            job_props.put("service_ping_timeout", ""+ServiceManagerComponent.meta_ping_timeout);
        }

        meta_props.put("service-class", ""+service_class.decode());
        meta_props.put("service-type", ""+service_type.decode());
        meta_props.put("stopped", ""+stopped);
        meta_props.put("service-state", ""+getServiceState());
        meta_props.put("ping-active", "false");
        meta_props.put("service-alive",      "false");
        meta_props.put("service-healthy",    "false");
        meta_props.put("service-statistics", "N/A");

        if ( isStartable() ) {
            meta_props.put("ping-only", "false");
        } else {
            meta_props.put("ping-only", "true");
        }
        // caller will save the meta props, **if** the rest of registration is ok.

        //UIMAFramework.getLogger(BaseUIMAAsynchronousEngineCommon_impl.class).setLevel(Level.OFF);
        //UIMAFramework.getLogger(BaseUIMAAsynchronousEngine_impl.class).setLevel(Level.OFF);
        // there are a couple junky messages that slip by the above configurations.  turn the whole danged thing off.
        UIMAFramework.getLogger().setLevel(Level.OFF);
    }

    DuccId getId()
    {
        return id;
    }

    protected void parseEndpoint(String ep)
    {
        if ( ep.startsWith(ServiceType.UimaAs.decode()) ) {
            int ndx = ep.indexOf(":");
            ep = ep.substring(ndx+1);
            ndx = ep.indexOf(":");

            this.endpoint = ep.substring(0, ndx).trim();
            this.broker = ep.substring(ndx+1).trim();
            this.service_type = ServiceType.UimaAs;

            URL url = null;
        try {               
        url = new URL(null, broker, new TcpStreamHandler());
      } catch (MalformedURLException e) {
                throw new IllegalArgumentException("Invalid broker URL: " + broker);
      }
            this.broker_host = url.getHost();
            this.broker_port = url.getPort();

            if ( this.endpoint.equals("") || this.broker.equals("") ) {
                throw new IllegalArgumentException("The endpoint cannot be parsed.  Expecting UIMA-AS:Endpoint:Broker, received " + key);
            }
        } else {
            this.service_type = ServiceType.Custom;
            int ndx = ep.indexOf(":");
            this.endpoint = ep.substring(ndx+1);
        }

    }

    synchronized void deleteProperties()
    {

        // be sure to move any services that seem not to have croaked yet to history
        String history = meta_props.getStringProperty(history_key, "");
        for ( Long id : friendly_ids.keySet() ) {
            history = history + " " + id.toString();
        }
        meta_props.put(history_key, history);
        meta_props.remove("implementors");

        ServiceManagerComponent.deleteProperties(id.toString(), meta_filename, meta_props, props_filename, job_props);
        meta_filename = null;
        props_filename = null;
    }

    void setIncoming(ServiceSet sset)
    {
        predecessors.add(sset);
    }

    void clearEdges()
    {
        predecessors.clear();
        successors.clear();
    }

    boolean isStopped()
    {
        return stopped;
    }

    boolean hasPredecessor()
    {
        return predecessors.size() != 0;
    }

    List<ServiceSet> getPredecessors()
    {
        return predecessors;
    }

    void removePredecessor(ServiceSet pred)
    {
        predecessors.remove(pred);
    }

    void setOutgoing(ServiceSet sset)
    {
        this.successors.add(sset);
    }

    List<ServiceSet> getSuccessors()
    {
        return new ArrayList<ServiceSet>(this.successors);
    }

    void removeSuccessor(ServiceSet succ)
    {
        successors.remove(succ);
    }

    boolean hasSuccessor()
    {
        return successors.size() != 0;
    }

    String[] getIndependentServices()
    {
        return independentServices;
    }

    // just for testing!
    void setIndependentServices(String[] ind)
    {
        this.independentServices = ind;
    }

    private void parseIndependentServices()
    {
        String depstr = job_props.getProperty(RegistrationOption.ServiceDependency.decode());
        String[] result = null;

        if ( depstr != null ) {
            result = depstr.split("\\s");
            for ( int i = 0; i < result.length; i++ ) {
                result[i] = result[i].trim();
            }
        }
        independentServices = result;
    }

    /**
     * A service is startable if it's UIMA-AS, or if it's CUSTOM and has a process_executable
     * associated with it.  A non-startable CUSTOM service may have only a pinger.
     */
    boolean isStartable()
    {
        switch ( service_type ) {
            case UimaAs:
                return true;
            case Custom:
                return job_props.containsKey("process_executable");
        }
        return false// redundant but needed for compilation
    }

    /**
     * At boot only ... synchronize my state with published OR state.
     */
    void synchronizeImplementors(Map<DuccId, JobState> work)
    {
        HashMap<Long, DuccId> newmap = new HashMap<Long, DuccId>();
        // first loop synchronized 'friendly_ids' with live jobs comining in
        for ( DuccId id : work.keySet() ) {

            long fid = id.getFriendly();
            if ( friendly_ids.containsKey(fid) ) {
                JobState js = work.get(id);
                implementors.put(id, js);
                newmap.put(fid, id);
            }
        }

        // second loop synchronizes history with jobs that used to be live and aren't any more (because of restart)
        String history = meta_props.getStringProperty(history_key, "");
        for ( Long friendly : friendly_ids.keySet() ) {
            DuccId id = newmap.get(friendly);
            if ( id == null ) {
                history = history + " " + friendly;
            }
        }
        meta_props.put(history_key, history);

        friendly_ids = newmap;                       // replace persisted version with validated version from OR state
        persistImplementors();
    }

    /**
     *
     */
    void enforceAutostart()
    {
      //String methodName = "enforceAutostart";
        if ( ! autostart ) return;                   // not doing auto, nothing to do
        if ( stopped     ) return;                   // doing auto, but we've been manually stopped
        if ( failure_run >= failure_max ) return;    // too  many failures, no more enforcement

        if ( (!isStartable()) && (serviceMeta == null) ) {    // ping-only and pinger not alive
            start();                                          // ... then it needs to be started
            return;
        }

        // could have more implementors than instances if some were started dynamically but the count not persisted
        int needed = Math.max(0, instances - countImplementors());

        while ( (needed--) > 0 ) {
            if ( ! start() ) break;
        }
    }

    /**
     * Add implementor that we have an OR-assigned DuccId for
     */
    public void addImplementor(DuccId id, JobState js)
    {
        if ( isSubmitted() ) {
            friendly_ids.put(id.getFriendly(), id);
        }
        implementors.put(id, js);
        persistImplementors();
    }

    void promote()
    {
        String methodName = "promote";
        logger.debug(methodName, null, "Promoting", key);
        switch ( service_class ) {
            case Implicit : this.service_class = ServiceClass.Submitted; break;
            case Submitted: break;
            default       : throw new IllegalStateException("Trying to promote a Registered service!");
        }
    }

    boolean isUimaAs()
    {
        return (service_type == ServiceType.UimaAs);
    }

    boolean isCustom()
    {
        return (service_type == ServiceType.Custom);
    }

    Map<DuccId, JobState> getImplementors()
    {
        return implementors;
    }

    DuccProperties getJobProperties()
    {
        return job_props;
    }

    DuccProperties getMetaProperties()
    {
        return meta_props;
    }

    boolean isImplicit()
    {
        return (service_class == ServiceClass.Implicit);
    }

    boolean isSubmitted()
    {
        return (service_class == ServiceClass.Submitted);
    }

    boolean isPingOnly()
    {
        return meta_props.containsKey("ping-only");
    }

    boolean isRegistered()
    {
        return (service_class == ServiceClass.Registered) && (!deregistered);
    }

    void setReferencedStart(boolean val)
    {
        this.referenced_start = val;
    }

    boolean isReferencedStart()
    {
        return this.referenced_start;
    }

    String getUser()
    {
        return user;
    }

    /**
     * True only if
     *   a) this is a registereed service, and
     *   b) it has been deregistered.
     * because you can't deregister a non-registered service.
     */
    boolean isDeregistered()
    {
        return (service_class == ServiceClass.Registered) && (deregistered);
    }

    void deregister()
    {
        deregistered = true;
    }

    String getMetaFilename()
    {
        return meta_filename;
    }

    String getPropsFilename()
    {
        return props_filename;
    }

    synchronized int getNInstances()
    {
        return instances;
    }

    synchronized void saveMetaProperties()
    {
        String methodName = "saveMetaProperties";

        if ( meta_filename == null ) {
            // if this is null it was deleted and this is some kind of lingering thread updating, that
            // we don't really want any more
            logger.warn(methodName, id, "Meta properties is deleted, bypassing attempt to save.");
            return;
        }

        meta_props.put("stopped", ""+stopped);
        meta_props.put("service-state", ""+ getServiceState());
        meta_props.put("ping-active", "" + (serviceMeta != null));
        meta_props.put("service-alive",      "false");
        meta_props.put("service-healthy",    "false");
        meta_props.put("service-statistics", "N/A");
       
        if ( serviceMeta != null ) {
            IServiceStatistics ss = serviceMeta.getServiceStatistics();
            if ( ss != null ) {
                meta_props.put("service-alive",      "" + ss.isAlive());
                meta_props.put("service-healthy",    "" + ss.isHealthy());
                meta_props.put("service-statistics", "" + ss.getInfo());
            }
        }
       
        FileOutputStream fos = null;
        try {           
      fos = new FileOutputStream(meta_filename);           
      meta_props.store(fos, "Meta descriptor");
    } catch (FileNotFoundException e) {
            logger.warn(methodName, id, "Cannot save meta properties, file does not exist.");
    } catch (IOException e) {
            logger.warn(methodName, id, "I/O Error saving meta properties:", e);
    } finally {
            try {
        if ( fos != null ) fos.close();
      } catch (IOException e) {
      }
        }
        return;
    }

    void saveServiceProperties()
    {
        String methodName = "saveServiceProperties";
        FileOutputStream fos = null;
        try {
          fos = new FileOutputStream(props_filename);
      job_props.store(fos, "Service descriptor");
    } catch (FileNotFoundException e) {
            logger.warn(methodName, id, "Cannot save service properties, file does not exist.");
    } catch (IOException e) {
            logger.warn(methodName, id, "I/O Error saving service properties:", e);
    } finally {
            try {
        if ( fos != null ) fos.close();
      } catch (IOException e) {
      }
        }
        return;
    }

    synchronized void setNInstances(int n)
    {
        if ( n != meta_props.getIntProperty("instances") ) {
            meta_props.setProperty("instances", Integer.toString(n));
            this.instances = n;
            saveMetaProperties();
        }
    }

    synchronized void setAutostart(boolean auto)
    {
        meta_props.setProperty("autostart", auto ? "true" : "false");
        this.autostart = auto;
        saveMetaProperties();
        if ( auto ) {
            // turning this on gives benefit of the doubt on failure management
            failure_run = 0;
        }
    }

    synchronized boolean isAutostart()
    {
        return autostart;
    }

    synchronized void persistImplementors()
    {
        if ( isImplicit() ) return;

        if ( friendly_ids.size() == 0 ) {
            meta_props.remove("implementors");
        } else {
            StringBuffer sb = new StringBuffer();
            for ( Long l : friendly_ids.keySet() ) {
                sb.append(Long.toString(l));
                sb.append(" ");
            }
            String s = sb.toString().trim();
            meta_props.setProperty("implementors", s);
        }
        saveMetaProperties();
    }

    synchronized void persistReferences()
    {
        if ( references.size() == 0 ) {
            meta_props.remove("references");
        } else {
            StringBuffer sb = new StringBuffer();
            for ( DuccId id : references.keySet() ) {
                sb.append(id.toString());
                sb.append(" ");
            }
            String s = sb.toString().trim();
            meta_props.setProperty("references", s);
        }
        saveMetaProperties();
    }

    /**
     * If we're are registered service, and one of the "stringids" from submit
     * happens to match the friendly id passed in then this ServiceSet is
     * the representative object for the service.
     */
    boolean matches(DuccId did)
    {
        if ( ! isRegistered() ) return false;

        if ( friendly_ids.containsKey(did.getFriendly()) ) {
            friendly_ids.put(did.getFriendly(), did);
            return true;
        }

        return false;           
    }

    boolean containsImplementor(DuccId id)
    {
        // must use friendly, in case the thing was just started and not into the implementors set yet
        return friendly_ids.containsKey(id.getFriendly());
    }

    public void removeImplementor(DuccId id)
    {
        String methodName = "removeImplementors";
        if ( ! implementors.containsKey(id ) ) return// quick short circuit if it's already gone

        logger.debug(methodName, this.id, "Removing implementor", id);
        implementors.remove(id);
        friendly_ids.remove(id.getFriendly());
        if ( implementors.size() == 0 ) {
            stopPingThread();
        }
        String history = meta_props.getStringProperty(history_key, "");
        history = history + " " + id.toString();
        meta_props.put(history_key, history);
        persistImplementors();

        //
        // So much to check here
        // -  all implementors gone?
        // - is it a registered, not-ping-only service, or a submitted service (startable)
        // - is it a uima-as service, with an internal pinger?
        // Only if all that is true, we'll clear out the queues.
        //==
        logger.debug(methodName, id, "implementors.size()", implementors.size(),
                     "service_class", service_class,
                     "isStartable()", isStartable(),
                     "isSubmitted()", isSubmitted(),
                     "service_type", service_type,
                     "ping_class", job_props.getStringProperty("service_ping_class", UimaAsPing.class.getName())
                     );

        // This block is to clear the service queue from ActiveMq if it's no longer being used.
        if ( implementors.size() == 0 ) {     // Went to 0 and there was a pinger?
            if ( ( (service_class == ServiceClass.Registered) && isStartable()) || isSubmitted() ) {   // Is one of our happy cases (not ping-only, we don't know much about it.)
                if ( service_type == ServiceType.UimaAs ) {
                    // Either no pinger specified, in which case the default is used.  Or, it is specified, and if it
                    // matches the default, we get to clear anyway.
                    String pingclass = job_props.getStringProperty("service_ping_class", UimaAsPing.class.getName());
                    if ( pingclass.equals(UimaAsPing.class.getName()) ) {
                        UimaAsServiceMonitor monitor = new UimaAsServiceMonitor(endpoint, broker_host, broker_jmx_port);
                        logger.debug(methodName, id, "Clearing queues");
                        try {
                          monitor.init(null);
              monitor.clearQueues();
            } catch (Throwable e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
                    }
                }
            }
        }
    }

    public synchronized int countImplementors()
    {
        // The implementos and friendly_ids sets track each other carefully.  The former
        // tracks process state via the remote process's DuccId.  The latter tracks
        // what we've tried to start, but may not know the actual state for until we get
        // Orchestrator publications.
        //
        // To avoid ugly races, we consider a process to be an implementor as soon as we
        // get the "friendly" id from the Orchestrator.
        return friendly_ids.size();
    }

    public synchronized int reference(DuccId id)
    {
        String methodName = "reference";
        logger.debug(methodName, this.id, " ---------------- Service ", this.id, "references", id);

        if ( linger != null ) {
            logger.debug(methodName, this.id, " ---------------- Canceling linger task");
            linger.cancel();
            linger = null;
        }
        references.put(id, id);

        persistReferences();
        return references.size();
    }

    public synchronized int dereference(DuccId id)
    {
        if ( references.remove(id) == null ) {
            throw new IllegalStateException("Id " + id + " not found in map for " + getKey());
        }

        // stop the pinger if no longer needed
        if ( (references.size() == 0) &&                          // nothing left
             ( isImplicit() || ( isCustom() && !isStartable()) )   // implicit UIMA-AS || implicit CUSTOM
             )
        {
            stopPingThread();                                     // must stop pinger because there's no state machine
                                                                  // to do it for us in this situation
        }

        persistReferences();
        return references.size();
    }

    public synchronized int countReferences()
    {
        // note that this could change as soon as you get it so don't count on it being correct
        // this is intended only for messages that don't have to be too accurate
        return references.size();
    }

    private ServiceState translateJobState(JobState js)
    {       
        switch ( js ) {
        case Received:        // Job has been vetted, persisted, and assigned unique Id
        case WaitingForDriver:    // Process Manager is launching Job Driver
        case WaitingForServices:  // Service Manager is checking/starting services for Job
                return ServiceState.Waiting;
        case WaitingForResources:  // Scheduler is assigning resources to Job
        case Initializing:      // Process Agents are initializing pipelines
                return ServiceState.Initializing;
        case Running:        // At least one Process Agent has reported process initialization complete
                return ServiceState.Available;
        case Completing:      // Job processing is completing
        case Completed:        // Job processing is completed
        case Undefined:        // None of the above
        default:
                return ServiceState.NotAvailable;
        }
    }

    /**
     * The MAX of the states of the implementors.
     */
    private ServiceState cumulativeJobState()
    {
        ServiceState response = ServiceState.NotAvailable;
        for ( JobState s : implementors.values() ) {
            ServiceState translated = translateJobState(s);
            if translated.ordinality() > response.ordinality() ) response = translated;
        }

        // if we're stopped, we cannot advance global state beyond whatever it is, i.e. an "active"
        // implementor that hasn't quit yet is not allowed to progress state.
        if ( isStopped() ) {
            ServiceState current = getServiceState();
            if ( current.ordinality() < response.ordinality() ) {
                response = current;
            }
        }
        return response;
    }

    // synchronized void establish()
//     {       
//         if ( implementors.size() == 0 ) {
//             startPingThread();
//         }
//         // Otherwise we let the form that passes in implementor state start the pinger
//     }

    public synchronized void establish()
    {
        String methodName = "establish.0";

        logger.debug(methodName, id, "service_class", service_class, "nimplementors", implementors.size(),
              "instances", instances);
        switch ( service_class ) {
            case Implicit:
                startPingThread();
                break;
            case Submitted:
                for ( DuccId id : implementors.keySet() ) {       // there's only one
                    establish(id, implementors.get(id));
                }
                break;
            case Registered:
                // use friendly to find number of potential instances because it reflects both
                // the number of actually running instances plus those just started but not yet
                // checked with their ducc ids yet.
               
                if ( friendly_ids.size() > 0 ) {
                    for ( DuccId id : implementors.keySet() ) {       // there's only one
                        establish(id, implementors.get(id));
                    }
                } else {
                    if ( service_type == ServiceType.Custom ) {
                        startPingThread();
                    } else {
                        int needed = Math.max(0, instances - friendly_ids.size());
                        while ( (needed--) > 0 ) {
                            start();
                        }
                    }
                }
                break;       
        }       
    }

    /**
     * Starts a ping thread because I've started up and I need to monitor myself.
     */
    public synchronized void establish(DuccId id, JobState job_state)
    {
        String methodName = "establish.1";

        if ( service_class == ServiceClass.Implicit ) {
            startPingThread();
            return;
        }

        if ( service_class == ServiceClass.Submitted ) {
            //
            // Annoying edge case - cancel a service, then resubmit before the first one has time
            // to wind down - need to be sure that the winding-down service is not handled by the
            // new instance's state machine.
            //
            if ( ! implementors.containsKey(id) ) {
                logger.debug(methodName, id, "Submitted service: Skipping state machine because the service set has no implemetor for", id);
                return;
            }
        }

        if ( true ) {
            implementors.put(id, job_state);
            ServiceState cumulative = cumulativeJobState();
            //
            // Note on the CUMULATIVE state: this is the cumulative state as determined by service processes.  If they
            // should all die at once through some temporary glitch the state could go to Unavailable even though the
            // SM would now be in active retry - the states below avoid regression state if CUMULATIVE goes to
            // Unavailable but the retry count indicates retry is still in progress.
            //

            //
            // The ping state is pretty much always the right state.  But if we're
            // not yet pinging we need to see if any of the implementors states
            // indicates we should be pinging, in which case, start the pinger.
            //
            logger.debug(methodName, id, "serviceState", getServiceState(), "cumulativeState", cumulative);
            switch ( getServiceState() ) {
                // If I'm brand new and something is initting then I can be too.  If something is
                // actually running then I can start a pinger which will set my state.
                case Undefined:
                    switch ( cumulative ) {
                        case Initializing:
                            setServiceState(ServiceState.Initializing);
                            break;
                        case Available:
                            startPingThread();
                            break;
                        case Waiting:
                            setServiceState(ServiceState.Waiting);
                            break;
                        case NotAvailable:
                        break;
                    }
                    break;

                    // If I'm initting and now something is running we can start a pinger
                case Initializing:
                    switch ( cumulative ) {
                        case Available:
                            startPingThread();
                            break;
                        case Initializing:
                            break;
                        case Waiting:
                            setServiceState(ServiceState.Initializing);
                            break;
                        case NotAvailable:
                            if ( failure_run >= failure_max ) {
                                setServiceState(ServiceState.NotAvailable);
                                stopPingThread();
                            } else {
                                // don't regress if we're in retry
                                logger.info(methodName, id, "RETRY RETRY RETRY prevents state regression from Initializing");
                            }
                          break;
                    }
                    break;

                case NotAvailable:
                    switch ( cumulative ) {
                        case Available:
                            startPingThread();
                            setServiceState(ServiceState.Available);
                            break;
                        case Initializing:
                            setServiceState(ServiceState.Initializing);
                            break;
                        case Waiting:
                            setServiceState(ServiceState.Waiting);
                            break;
                        case NotAvailable:
                            break;
                    }
                    break;

                case Available:
                    switch ( cumulative ) {
                        case Available:
                            startPingThread();
                            break;
                        case Initializing:
                            // Not immediately clear what would cause this other than an error but let's not crash.
                            logger.warn(methodName, id, "STATE REGRESSION:", getServiceState(), "->", cumulative); // can't do anything about it but complain
                            setServiceState(ServiceState.Initializing);
                            break;
                        case NotAvailable:
                            if ( failure_run >= failure_max ) {
                                setServiceState(ServiceState.NotAvailable);
                                stopPingThread();
                            } else {
                                // don't regress if we're in retry
                                logger.info(methodName, id, "RETRY RETRY RETRY prevents state regression from Available");
                            }
                            break;
                    }

                    break;

                case Waiting:                   
                    switch ( cumulative ) {
                        case Available:
                            break;
                        case Initializing:
                            // state regression can happen with a promoted implicit service, where the service is referenced
                            // and a pinger starts before the actual service is available.  the ping keeps us in 'waiting' state
                            // up until expiration, but if the service is actually initializing, we regress.
                            logger.warn(methodName, id, "STATE REGRESSION:", getServiceState(), "->", cumulative); // can't do anything about it but complain
                            setServiceState(ServiceState.Initializing);
                            break;
                        case Waiting:
                            break;
                        case NotAvailable:  
                            if ( failure_run >= failure_max ) {
                                setServiceState(ServiceState.NotAvailable);
                            } else {
                                // don't regress if we're in retry
                                logger.info(methodName, id, "RETRY RETRY RETRY prevents state regression from Waiting");
                            }
                            stopPingThread();
                            break;
                    }
                    break;

                case Stopping:
                    switch ( cumulative ) {
                        case Available:               
                        case Initializing:
                        case Waiting:                           
                            break;

                        case NotAvailable:               
                            // all the implementors died finally
                            setServiceState(ServiceState.NotAvailable);
                            break;
                    }
                    break;                   
            }
        }

    }

    synchronized ServiceState getServiceState()
    {
        return service_state;
    }

    synchronized void setServiceState(ServiceState ss)
    {
        this.service_state = ss;
        saveMetaProperties();
    }

    synchronized String getKey()
    {
        return key;
    }

    void resetRunFailures()
    {
        failure_run = 0;
    }

    synchronized boolean excessiveRunFailures()
    {
        String methodName = "runFailures";
        if ( (++failure_run) >= failure_max ) {
            logger.debug(methodName, id, "RUN FAILURES EXCEEDED");
            return true;
        }
        logger.debug(methodName, id, "RUN FAILURES NOT EXCEEDED YET", failure_run);
        return false;
    }

    private void startPingThread()
    {
      String methodName = "startPingThread";
        if ( serviceMeta != null ) return;         // don't start multiple times.

        try {
            logger.info(methodName, id, "Starting ping/monitor.");
            serviceMeta = new PingDriver(this);
        } catch ( Throwable t ) {
            logger.error(methodName, id, "Cannot instantiate ping/monitor.", t);
            return;
        }

        setServiceState(ServiceState.Waiting);
        Thread t = new Thread(serviceMeta);
        t.start();
    }

    synchronized void pingExited()
    {
        String methodName = "pingExited";

        if ( serviceMeta != null ) {
            logger.warn(methodName, id, "Pinger exited voluntarily, setting state to Undefined. Endpoint", endpoint);
            setServiceState(ServiceState.Undefined);    // not really sure what state is. it will be

            // checked and updated next run through the
            // main state machine, and maybe ping restarted.
            residualMeta = serviceMeta;
            serviceMeta = null;
        } else {
            if ( ! isStopped() ) {                      // state may still be Stopping, don't over regress state
                setServiceState(ServiceState.NotAvailable);
            }
        }

        if ( isImplicit() ) {
            deleteProperties();
        } else {
            saveMetaProperties();
        }       
    }

    public synchronized void stopPingThread()
    {
        String methodName = "stopPingThread";

        if ( serviceMeta != null ) {
            logger.debug(methodName, id, "Stopping ping thread, endpoint", endpoint);
            serviceMeta.stop();
            residualMeta = serviceMeta;
            serviceMeta = null;
        }

        if ( !isRegistered() ) {
            deleteProperties();
        } else {
            saveMetaProperties();
        }
    }

    synchronized void setResponsive()
    {
        setServiceState(ServiceState.Available);
        saveMetaProperties();
    }

    synchronized void setUnresponsive()
    {
        setServiceState(ServiceState.NotAvailable);
        saveMetaProperties();
    }

    synchronized void setWaiting()
    {
        //
        // Only switch state sometimes ....
        //
        switch ( getServiceState() ) {
            case Available:   
            case NotAvailable:
            case Undefined:   
                setServiceState(ServiceState.Waiting);
                break;
            case Initializing:
            case Waiting:     
            case Stopping:    
                break;
        }
    }

//    boolean ping()
//    {
//      //String methodName = "ping";
//        boolean answer = true;
//
//        // Instantiate Uima AS Client
//        BaseUIMAAsynchronousEngine_impl uimaAsEngine = new BaseUIMAAsynchronousEngine_impl();
//        Map<String, Object> appCtx = new HashMap<String, Object>();
//        appCtx.put(UimaAsynchronousEngine.ServerUri, broker);
//        appCtx.put(UimaAsynchronousEngine.Endpoint, endpoint);
//        appCtx.put(UimaAsynchronousEngine.GetMetaTimeout, ServiceManagerComponent.meta_ping_timeout);  // 500 ms should be enough to get GetMeta reply
//
//        try {
//            //  this sends GetMeta request and blocks waiting for a reply
//            uimaAsEngine.initialize(appCtx);
//            // logger.info(methodName, null, "Dependent Service Available:", getKey());
//        } catch( ResourceInitializationException e) {
//            //  either broker is down or service not available
//            // logger.error(methodName, null, "Remote service unavailable:", getKey());
//            answer = false;            
//        } finally {
//            uimaAsEngine.stop();
//        }
//
//        return answer;
//    }


    void log_text(String logdir, String text)
    {
      String methodName = "log_text";
        String[] args = {
            System.getProperty("ducc.agent.launcher.ducc_spawn_path"),
            "-u",
            user,
            "-f",
            logdir + "/service.err.log",
            "-a",
            "--",
            text
        };

        ProcessBuilder pb = new ProcessBuilder(args);
        try {
            Process p = pb.start();
            int rc = p.waitFor();
            logger.debug(methodName, null, "Log start errors returns with rc", rc);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    void log_errors(List<String> outlines, List<String> errlines)
    {

        Date date= new Date();
        String ts = (new Timestamp(date.getTime())).toString();

        String logdir = job_props.getProperty(UiOption.LogDirectory.pname());

        StringBuffer buf = new StringBuffer();

        // header
        buf.append("==========");
        buf.append(" Instance Startup Failure (stdout) ");
        buf.append(ts);
        buf.append(" ========================================\n");

        // stdout
        for ( String s : outlines ) {
            buf.append(s);
            buf.append("\n");
        }
        log_text(logdir, buf.toString());
       
        buf = new StringBuffer();
        buf.append("----------");
        buf.append("(stderr) ");
        buf.append(ts);
        buf.append(" ----------------------------------------\n");
        for ( String s : errlines ) {
            buf.append(s);
            buf.append("\n");
        }
        buf.append("==========");
        buf.append(" End Startup Failure ");
        buf.append(ts);
        buf.append(" ========================================\n");
        log_text(logdir, buf.toString());
    }


    /**
     * This assumes the caller has already verified that I'm a registered service.
     */
    boolean start()
    {
      String methodName = "start";

        logger.debug(methodName, null, "START START START START START START START START");
        this.stopped = false;

        if ( ! isStartable() ) {
            establish()// this will just start the ping thread
            return true;
        }

        // Simple use of ducc_ling, just submit as the user.  The specification will have the working directory
        // and classpath needed for the service, handled by the Orchestrator and Job Driver.
        String[] args = {
            System.getProperty("ducc.agent.launcher.ducc_spawn_path"),
            "-u",
            user,
            "--",
            System.getProperty("ducc.jvm"),
            "-cp",
            System.getProperty("java.class.path"),
            "org.apache.uima.ducc.cli.DuccServiceSubmit",
            "--specification",
            props_filename
        };
           
        for ( int i = 0; i < args.length; i++ ) {
            if ( i > 0 && (args[i-1].equals("-cp") ) ) {
                // The classpaths can be just awful filling the logs with junk.  It will end up in the agent log
                // anyway so let's inhibit it here.
                logger.debug(methodName, null, "Args[", i, "]: <CLASSPATH>");
            } else {
                logger.debug(methodName, null, "Args[", i, "]:", args[i]);
            }
        }

        ProcessBuilder pb = new ProcessBuilder(args);
        Map<String, String> env = pb.environment();
        env.put("DUCC_HOME", System.getProperty("DUCC_HOME"));

        ArrayList<String> stdout_lines = new ArrayList<String>();
        ArrayList<String> stderr_lines = new ArrayList<String>();
    try {
      Process p = pb.start();

            int rc = p.waitFor();
            logger.debug(methodName, null, "DuccServiceSubmit returns with rc", rc);

            // TODO: we should attache these streams to readers in threads because too much output
            //       can cause blocking, deadlock, ugliness.
      InputStream stdout = p.getInputStream();
      InputStream stderr = p.getErrorStream();
      BufferedReader stdout_reader = new BufferedReader(new InputStreamReader(stdout));
      BufferedReader stderr_reader = new BufferedReader(new InputStreamReader(stderr));
      String line = null;
      while ( (line = stdout_reader.readLine()) != null ) {
          stdout_lines.add(line);
      }

      line = null;
      while ( (line = stderr_reader.readLine()) != null ) {
          stderr_lines.add(line);
      }

    } catch (Throwable t) {
      // TODO Auto-generated catch block
            logger.error(methodName, null, t);
    }

        for ( String s : stderr_lines ) {
            logger.info(methodName, id, "Start stderr:", s);
        }

        // That was annoying.  Now search the lines for some hint of the id.
        boolean inhibit_cp = false;
        boolean started = false;
        StringBuffer submit_buffer = new StringBuffer();
        boolean recording = false;
        for ( String s : stdout_lines ) {

            // simple logic to inhibit printing the danged classpath
            if ( inhibit_cp ) {
                inhibit_cp = false;
                logger.info(methodName, id, "<INHIBITED CP>");
            } else {
                logger.info(methodName, id, "Start stdout:", s);
            }

            if ( s.indexOf("-cp") >= 0 ) {
                inhibit_cp = true;
            }

            if ( recording ) {
                submit_buffer.append(s.trim());
                submit_buffer.append(";");
            }
            if ( s.startsWith("1001 Command launching...") ) {
                recording = true;
                continue;
            }

            if ( s.startsWith("Service") && s.endsWith("submitted") ) {
                String[] toks = s.split("\\s");
                long friendly = 0;
                try {
                    friendly = Long.parseLong(toks[1]);
                    friendly_ids.put(friendly, null);
                    persistImplementors();
                    started = true;
                    logger.info(methodName, null, "Request to start service " + id.toString() + " accepted as job ", friendly);
                } catch ( NumberFormatException e ) {
                    logger.warn(methodName, null,  "Request to start service " + id.toString() + " failed, can't interpret response.: " + s);
                }

            }
        }

        boolean rc = true;
        if ( ! started ) {
            logger.warn(methodName, null, "Request to start service " + id.toString() + " failed.");
            meta_props.put("submit_error", submit_buffer.toString());
            setAutostart(false);
            log_errors(stdout_lines, stderr_lines);
        } else {
            meta_props.remove("submit_error");
            setServiceState(ServiceState.Initializing);
        }
        saveMetaProperties();
        logger.debug(methodName, null, "ENDSTART ENDSTART ENDSTART ENDSTART ENDSTART ENDSTART");
        return rc;
    }

    /**
     * This assumes the caller has already verified that I'm a registered service.
     */
    void stopOneProcess(DuccId id)
    {
        String methodName = "stop";
       
        String[] args = {
            System.getProperty("ducc.agent.launcher.ducc_spawn_path"),
            "-u",
            user,
            "--",
            System.getProperty("ducc.jvm"),
            "-cp",
            System.getProperty("java.class.path"),
            "org.apache.uima.ducc.cli.DuccServiceCancel",
            "--id",
            id.toString()
        };
       
        for ( int i = 0; i < args.length; i++ ) {
            if ( i > 0 && (args[i-1].equals("-cp") ) ) {
                // The classpaths can be just awful filling the logs with junk.  It will end up in the agent log
                // anyway so let's inhibit it here.
                logger.debug(methodName, null, "Args[", i, "]: <CLASSPATH>");
            } else {
                logger.debug(methodName, null, "Args[", i, "]:", args[i]);
            }
        }
       
        ProcessBuilder pb = new ProcessBuilder(args);
        Map<String, String> env = pb.environment();
        env.put("DUCC_HOME", System.getProperty("DUCC_HOME"));
           
        ArrayList<String> stdout_lines = new ArrayList<String>();
        ArrayList<String> stderr_lines = new ArrayList<String>();
        try {
            Process p = pb.start();
               
            int rc = p.waitFor();
            logger.debug(methodName, null, "DuccServiceCancel returns with rc", rc);

            InputStream stdout = p.getInputStream();
            InputStream stderr = p.getErrorStream();
            BufferedReader stdout_reader = new BufferedReader(new InputStreamReader(stdout));
            BufferedReader stderr_reader = new BufferedReader(new InputStreamReader(stderr));

            String line = null;
            while ( (line = stdout_reader.readLine()) != null ) {
                stdout_lines.add(line);
            }
               
            line = null;
            while ( (line = stderr_reader.readLine()) != null ) {
                stderr_lines.add(line);
            }
               
        } catch (Throwable t) {
            // TODO Auto-generated catch block
            logger.error(methodName, null, t);
        }

        boolean inhibit_cp = false;
        for ( String s : stdout_lines ) {
            // simple logic to inhibit printing the danged classpath
            if ( inhibit_cp ) {
                inhibit_cp = false;
                logger.info(methodName, id, "<INHIBITED CP>");
            } else {
                logger.info(methodName, id, "Stop stdout:", s);
            }
           
            if ( s.indexOf("-cp") >= 0 ) {
                inhibit_cp = true;
            }
        }
           
        for ( String s : stderr_lines ) {
            logger.info(methodName, id, "Stop stderr:", s);
        }

        // is this the last implementor?  if so the service is no longer available.
        // should not have to do this, the state should update correctly in the state machine
        //if ( implementors.size() <= 1 ) {
        //    service_state = ServiceState.NotAvailable;
        //}
        //return new ServiceReplyEvent(ServiceCode.OK, "Start service " + id.toString() + " complete.", toks[1], null);           
       
    }

    /**
     * Stop everything
     */
    void stop()
    {
        String methodName = "stop";
        logger.debug(methodName, id, "Stopping all implementors");
        this.stopped = true;
        stopPingThread();
        setServiceState(ServiceState.Stopping);

        if ( ! isStartable() ) {         // CUSTOM ping-only service
            return;
        }

        for ( DuccId id : implementors.keySet() ) {
            stopOneProcess(id);
        }
        saveMetaProperties();
    }

    /**
     * Stop 'count' services.
     * TODO: Put in logic to stop intelligently, i.e. favor processes not yet running
     */
    void stop(int count)
    {
        String methodName = "stop(count)";

        if ( ! isStartable() ) {         // CUSTOM ping-only, only one running, let common code do the honors
            stop();
            return;
        }

        logger.debug(methodName, id, "Stopping", count, "implementors");
        for ( DuccId id: implementors.keySet() ) {
            if ( (count--) > 0 ) {
                stopOneProcess(id);
            } else {
                break;
            }
        }
        saveMetaProperties();
    }


    private class LingerTask
        extends TimerTask
    {
        ServiceSet sset;
        LingerTask(ServiceSet sset)
        {       
            String methodName = "LingerTask.init";
            logger.debug(methodName, id, "Linger starts", linger_time);
            this.sset = sset;
        }

        public void run()
        {
            String methodName = "LingerTask.run";
            logger.debug(methodName, id, "Lingering stop completes.");
            // doesn't matter how its started i think, we have to set this flag off when we stop
            sset.setReferencedStart(false);
            linger = null;
            sset.stop();
        }
    }

    void lingeringStop()
    {
        if ( timer == null ) {
            timer = new Timer();
        }
        linger = new LingerTask(this);
        timer.schedule(linger, linger_time);
    }

    IServiceDescription query()
    {
        IServiceDescription sd = new ServiceDescription();
       
        ArrayList<ADuccId> imp = new ArrayList<ADuccId>();
        for ( DuccId id : implementors.keySet() ) {
            imp.add(id);
        }
        sd.setImplementors(imp);

        ArrayList<ADuccId> ref = new ArrayList<ADuccId>();
        ref.clear();
        for ( DuccId id : references.keySet() ) {
            ref.add(id);
        }
        sd.setReferences(ref);

        sd.setInstances(getNInstances());

        sd.setType(service_type);
        sd.setSubclass(service_class);
        sd.setEndpoint(endpoint);
        sd.setBroker(broker);
        sd.setServiceState(getServiceState());
        //sd.setJobState(job_state);
        sd.setActive(serviceMeta != null);
        sd.setStopped(stopped);
        sd.setAutostart(autostart);
        sd.setLinger(linger_time);
        sd.setId(id);
        sd.setUser(user);
        sd.setDeregistered(isDeregistered());

        if ( serviceMeta != null ) {
            sd.setQueueStatistics(serviceMeta.getServiceStatistics());
        }

        return sd;
    }


    /**
     * For debugging, so it's easier to identify this guy in the eclipse debugger.
     */
    public String toString()
    {
        return endpoint;
    }


}

/**
    For reference

  public enum JobState {
    Received,        // Job has been vetted, persisted, and assigned unique Id
    WaitingForDriver,    // Process Manager is launching Job Driver
    WaitingForServices,    // Service Manager is checking/starting services for Job
    WaitingForResources,  // Scheduler is assigning resources to Job
    Initializing,      // Process Agents are initializing pipelines
    Running,        // At least one Process Agent has reported process initialization complete
    Completing,        // Job processing is completing
    Completed,        // Job processing is completed
    Undefined        // None of the above
  };
*/ 
TOP

Related Classes of org.apache.uima.ducc.sm.ServiceSet

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.